PageRenderTime 43ms CodeModel.GetById 14ms app.highlight 25ms RepoModel.GetById 0ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre14/org/gjt/sp/jedit/syntax/TokenMarker.java

#
Java | 874 lines | 635 code | 109 blank | 130 comment | 179 complexity | 96611e0e232400ec325fd798fccbdb48 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0
  1/*
  2 * TokenMarker.java - Tokenizes lines of text
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 * :folding=explicit:collapseFolds=1:
  5 *
  6 * Copyright (C) 1998, 2003 Slava Pestov
  7 * Copyright (C) 1999, 2000 mike dillon
  8 *
  9 * This program is free software; you can redistribute it and/or
 10 * modify it under the terms of the GNU General Public License
 11 * as published by the Free Software Foundation; either version 2
 12 * of the License, or any later version.
 13 *
 14 * This program is distributed in the hope that it will be useful,
 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 17 * GNU General Public License for more details.
 18 *
 19 * You should have received a copy of the GNU General Public License
 20 * along with this program; if not, write to the Free Software
 21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 22 */
 23
 24package org.gjt.sp.jedit.syntax;
 25
 26//{{{ Imports
 27import gnu.regexp.*;
 28import javax.swing.text.Segment;
 29import java.util.*;
 30import org.gjt.sp.jedit.*;
 31import org.gjt.sp.util.CharIndexedSegment;
 32import org.gjt.sp.util.Log;
 33//}}}
 34
 35/**
 36 * A token marker splits lines of text into tokens. Each token carries
 37 * a length field and an identification tag that can be mapped to a color
 38 * or font style for painting that token.
 39 *
 40 * @author Slava Pestov, mike dillon
 41 * @version $Id: TokenMarker.java 4947 2003-12-27 05:14:46Z spestov $
 42 *
 43 * @see org.gjt.sp.jedit.syntax.Token
 44 * @see org.gjt.sp.jedit.syntax.TokenHandler
 45 */
 46public class TokenMarker
 47{
 48	//{{{ TokenMarker constructor
 49	public TokenMarker()
 50	{
 51		ruleSets = new Hashtable(64);
 52	} //}}}
 53
 54	//{{{ addRuleSet() method
 55	public void addRuleSet(ParserRuleSet rules)
 56	{
 57		ruleSets.put(rules.getSetName(), rules);
 58
 59		if (rules.getSetName().equals("MAIN"))
 60			mainRuleSet = rules;
 61	} //}}}
 62
 63	//{{{ getMainRuleSet() method
 64	public ParserRuleSet getMainRuleSet()
 65	{
 66		return mainRuleSet;
 67	} //}}}
 68
 69	//{{{ getRuleSet() method
 70	public ParserRuleSet getRuleSet(String setName)
 71	{
 72		return (ParserRuleSet) ruleSets.get(setName);
 73	} //}}}
 74
 75	//{{{ getRuleSets() method
 76	/**
 77	 * @since jEdit 4.2pre3
 78	 */
 79	public ParserRuleSet[] getRuleSets()
 80	{
 81		return (ParserRuleSet[])ruleSets.values().toArray(new ParserRuleSet[ruleSets.size()]);
 82	} //}}}
 83
 84	//{{{ markTokens() method
 85	/**
 86	 * Do not call this method directly; call Buffer.markTokens() instead.
 87	 */
 88	public LineContext markTokens(LineContext prevContext,
 89		TokenHandler tokenHandler, Segment line)
 90	{
 91		//{{{ Set up some instance variables
 92		// this is to avoid having to pass around lots and lots of
 93		// parameters.
 94		this.tokenHandler = tokenHandler;
 95		this.line = line;
 96
 97		lastOffset = line.offset;
 98		lineLength = line.count + line.offset;
 99
100		context = new LineContext();
101
102		if(prevContext == null)
103			context.rules = getMainRuleSet();
104		else
105		{
106			context.parent = prevContext.parent;
107			context.inRule = prevContext.inRule;
108			context.rules = prevContext.rules;
109			context.spanEndSubst = prevContext.spanEndSubst;
110		}
111
112		keywords = context.rules.getKeywords();
113		escaped = false;
114
115		seenWhitespaceEnd = false;
116		whitespaceEnd = line.offset;
117		//}}}
118
119		//{{{ Main parser loop
120		ParserRule rule;
121		int terminateChar = context.rules.getTerminateChar();
122		boolean terminated = false;
123
124main_loop:	for(pos = line.offset; pos < lineLength; pos++)
125		{
126			//{{{ check if we have to stop parsing
127			if(terminateChar >= 0 && pos - line.offset >= terminateChar
128				&& !terminated)
129			{
130				terminated = true;
131				context = new LineContext(ParserRuleSet
132					.getStandardRuleSet(context.rules
133					.getDefault()),context);
134				keywords = context.rules.getKeywords();
135			} //}}}
136
137			//{{{ check for end of delegate
138			if(context.parent != null)
139			{
140				rule = context.parent.inRule;
141				if(rule != null)
142				{
143					if(checkDelegateEnd(rule))
144					{
145						seenWhitespaceEnd = true;
146						continue main_loop;
147					}
148				}
149			} //}}}
150
151			//{{{ check every rule
152			char ch = line.array[pos];
153
154			rule = context.rules.getRules(ch);
155			while(rule != null)
156			{
157				// stop checking rules if there was a match
158				if (handleRule(rule,false))
159				{
160					seenWhitespaceEnd = true;
161					continue main_loop;
162				}
163
164				rule = rule.next;
165			} //}}}
166
167			//{{{ check if current character is a word separator
168			if(Character.isWhitespace(ch))
169			{
170				if(!seenWhitespaceEnd)
171					whitespaceEnd = pos + 1;
172
173				if(context.inRule != null)
174					handleRule(context.inRule,true);
175
176				handleNoWordBreak();
177
178				markKeyword(false);
179
180				if(lastOffset != pos)
181				{
182					tokenHandler.handleToken(line,
183						context.rules.getDefault(),
184						lastOffset - line.offset,
185						pos - lastOffset,
186						context);
187				}
188
189				tokenHandler.handleToken(line,
190					context.rules.getDefault(),
191					pos - line.offset,1,context);
192				lastOffset = pos + 1;
193
194				escaped = false;
195			}
196			else
197			{
198				if(keywords != null || context.rules.getRuleCount() != 0)
199				{
200					String noWordSep = context.rules.getNoWordSep();
201
202					if(!Character.isLetterOrDigit(ch)
203						&& noWordSep.indexOf(ch) == -1)
204					{
205						if(context.inRule != null)
206							handleRule(context.inRule,true);
207
208						handleNoWordBreak();
209
210						markKeyword(true);
211
212						tokenHandler.handleToken(line,
213							context.rules.getDefault(),
214							lastOffset - line.offset,1,
215							context);
216						lastOffset = pos + 1;
217					}
218				}
219
220				seenWhitespaceEnd = true;
221				escaped = false;
222			} //}}}
223		} //}}}
224
225		//{{{ Mark all remaining characters
226		pos = lineLength;
227
228		if(context.inRule != null)
229			handleRule(context.inRule,true);
230
231		handleNoWordBreak();
232		markKeyword(true);
233		//}}}
234
235		//{{{ Unwind any NO_LINE_BREAK parent delegates
236unwind:		while(context.parent != null)
237		{
238			rule = context.parent.inRule;
239			if((rule != null && (rule.action
240				& ParserRule.NO_LINE_BREAK) == ParserRule.NO_LINE_BREAK)
241				|| terminated)
242			{
243				context = context.parent;
244				keywords = context.rules.getKeywords();
245				context.inRule = null;
246			}
247			else
248				break unwind;
249		} //}}}
250
251		tokenHandler.handleToken(line,Token.END,
252			pos - line.offset,0,context);
253
254		context = context.intern();
255		tokenHandler.setLineContext(context);
256		return context;
257	} //}}}
258
259	//{{{ Private members
260
261	//{{{ Instance variables
262	private Hashtable ruleSets;
263	private ParserRuleSet mainRuleSet;
264
265	// Instead of passing these around to each method, we just store them
266	// as instance variables. Note that this is not thread-safe.
267	private TokenHandler tokenHandler;
268	private Segment line;
269	private LineContext context;
270	private KeywordMap keywords;
271	private Segment pattern = new Segment();
272	private int lastOffset;
273	private int lineLength;
274	private int pos;
275	private boolean escaped;
276
277	private int whitespaceEnd;
278	private boolean seenWhitespaceEnd;
279	//}}}
280
281	//{{{ checkDelegateEnd() method
282	private boolean checkDelegateEnd(ParserRule rule)
283	{
284		if(rule.end == null)
285			return false;
286
287		LineContext tempContext = context;
288		context = context.parent;
289		keywords = context.rules.getKeywords();
290		boolean tempEscaped = escaped;
291		boolean b = handleRule(rule,true);
292		context = tempContext;
293		keywords = context.rules.getKeywords();
294
295		if(b && !tempEscaped)
296		{
297			if(context.inRule != null)
298				handleRule(context.inRule,true);
299
300			markKeyword(true);
301
302			context = (LineContext)context.parent.clone();
303
304			tokenHandler.handleToken(line,
305				(context.inRule.action & ParserRule.EXCLUDE_MATCH)
306				== ParserRule.EXCLUDE_MATCH
307				? context.rules.getDefault()
308				: context.inRule.token,
309				pos - line.offset,pattern.count,context);
310
311			keywords = context.rules.getKeywords();
312			context.inRule = null;
313			lastOffset = pos + pattern.count;
314
315			// move pos to last character of match sequence
316			pos += (pattern.count - 1);
317
318			return true;
319		}
320
321		// check escape rule of parent
322		if((rule.action & ParserRule.NO_ESCAPE) == 0)
323		{
324			ParserRule escape = context.parent.rules.getEscapeRule();
325			if(escape != null && handleRule(escape,false))
326				return true;
327		}
328
329		return false;
330	} //}}}
331
332	//{{{ handleRule() method
333	/**
334	 * Checks if the rule matches the line at the current position
335	 * and handles the rule if it does match
336	 */
337	private boolean handleRule(ParserRule checkRule, boolean end)
338	{
339		//{{{ Some rules can only match in certain locations
340		if(!end)
341		{
342			if(Character.toUpperCase(checkRule.hashChar)
343				!= Character.toUpperCase(line.array[pos]))
344			{
345				return false;
346			}
347		}
348
349		int offset = ((checkRule.action & ParserRule.MARK_PREVIOUS) != 0) ?
350			lastOffset : pos;
351		int posMatch = (end ? checkRule.endPosMatch : checkRule.startPosMatch);
352
353		if((posMatch & ParserRule.AT_LINE_START)
354			== ParserRule.AT_LINE_START)
355		{
356			if(offset != line.offset)
357				return false;
358		}
359		else if((posMatch & ParserRule.AT_WHITESPACE_END)
360			== ParserRule.AT_WHITESPACE_END)
361		{
362			if(offset != whitespaceEnd)
363				return false;
364		}
365		else if((posMatch & ParserRule.AT_WORD_START)
366			== ParserRule.AT_WORD_START)
367		{
368			if(offset != lastOffset)
369				return false;
370		} //}}}
371
372		int matchedChars = 1;
373		CharIndexedSegment charIndexed = null;
374		REMatch match = null;
375
376		//{{{ See if the rule's start or end sequence matches here
377		if(!end || (checkRule.action & ParserRule.MARK_FOLLOWING) == 0)
378		{
379			// the end cannot be a regular expression
380			if((checkRule.action & ParserRule.REGEXP) == 0 || end)
381			{
382				if(end)
383				{
384					if(context.spanEndSubst != null)
385						pattern.array = context.spanEndSubst;
386					else
387						pattern.array = checkRule.end;
388				}
389				else
390					pattern.array = checkRule.start;
391				pattern.offset = 0;
392				pattern.count = pattern.array.length;
393				matchedChars = pattern.count;
394
395				if(!SyntaxUtilities.regionMatches(context.rules
396					.getIgnoreCase(),line,pos,pattern.array))
397				{
398					return false;
399				}
400			}
401			else
402			{
403				// note that all regexps start with \A so they only
404				// match the start of the string
405				int matchStart = pos - line.offset;
406				charIndexed = new CharIndexedSegment(line,matchStart);
407				match = checkRule.startRegexp.getMatch(
408					charIndexed,0,RE.REG_ANCHORINDEX);
409				if(match == null)
410					return false;
411				else if(match.getStartIndex() != 0)
412					throw new InternalError("Can't happen");
413				else
414				{
415					matchedChars = match.getEndIndex();
416					/* workaround for hang if match was
417					 * zero-width. not sure if there is
418					 * a better way to handle this */
419					if(matchedChars == 0)
420						matchedChars = 1;
421				}
422			}
423		} //}}}
424
425		//{{{ Check for an escape sequence
426		if((checkRule.action & ParserRule.IS_ESCAPE) == ParserRule.IS_ESCAPE)
427		{
428			if(context.inRule != null)
429				handleRule(context.inRule,true);
430
431			escaped = !escaped;
432			pos += pattern.count - 1;
433		}
434		else if(escaped)
435		{
436			escaped = false;
437			pos += pattern.count - 1;
438		} //}}}
439		//{{{ Handle start of rule
440		else if(!end)
441		{
442			if(context.inRule != null)
443				handleRule(context.inRule,true);
444
445			markKeyword((checkRule.action & ParserRule.MARK_PREVIOUS)
446				!= ParserRule.MARK_PREVIOUS);
447
448			switch(checkRule.action & ParserRule.MAJOR_ACTIONS)
449			{
450			//{{{ SEQ
451			case ParserRule.SEQ:
452				context.spanEndSubst = null;
453
454				if((checkRule.action & ParserRule.REGEXP) != 0)
455				{
456					handleTokenWithSpaces(tokenHandler,
457						checkRule.token,
458						pos - line.offset,
459						matchedChars,
460						context);
461				}
462				else
463				{
464					tokenHandler.handleToken(line,
465						checkRule.token,
466						pos - line.offset,
467						matchedChars,context);
468				}
469
470				// a DELEGATE attribute on a SEQ changes the
471				// ruleset from the end of the SEQ onwards
472				if(checkRule.delegate != null)
473				{
474					context = new LineContext(
475						checkRule.delegate,
476						context.parent);
477					keywords = context.rules.getKeywords();
478				}
479				break;
480			//}}}
481			//{{{ SPAN, EOL_SPAN
482			case ParserRule.SPAN:
483			case ParserRule.EOL_SPAN:
484				context.inRule = checkRule;
485
486				byte tokenType = ((checkRule.action & ParserRule.EXCLUDE_MATCH)
487					== ParserRule.EXCLUDE_MATCH
488					? context.rules.getDefault() : checkRule.token);
489
490				if((checkRule.action & ParserRule.REGEXP) != 0)
491				{
492					handleTokenWithSpaces(tokenHandler,
493						tokenType,
494						pos - line.offset,
495						matchedChars,
496						context);
497				}
498				else
499				{
500					tokenHandler.handleToken(line,tokenType,
501						pos - line.offset,
502						matchedChars,context);
503				}
504
505				char[] spanEndSubst = null;
506				/* substitute result of matching the rule start
507				 * into the end string.
508				 *
509				 * eg, in shell script mode, <<\s*(\w+) is
510				 * matched into \<$1\> to construct rules for
511				 * highlighting read-ins like this <<EOF
512				 * ...
513				 * EOF
514				 */
515				if(charIndexed != null && checkRule.end != null)
516				{
517					spanEndSubst = substitute(match,
518						checkRule.end);
519				}
520
521				context.spanEndSubst = spanEndSubst;
522				context = new LineContext(
523					checkRule.delegate,
524					context);
525				keywords = context.rules.getKeywords();
526
527				break;
528			//}}}
529			//{{{ MARK_FOLLOWING
530			case ParserRule.MARK_FOLLOWING:
531				tokenHandler.handleToken(line,(checkRule.action
532					& ParserRule.EXCLUDE_MATCH)
533					== ParserRule.EXCLUDE_MATCH ?
534					context.rules.getDefault()
535					: checkRule.token,pos - line.offset,
536					pattern.count,context);
537
538				context.spanEndSubst = null;
539				context.inRule = checkRule;
540				break;
541			//}}}
542			//{{{ MARK_PREVIOUS
543			case ParserRule.MARK_PREVIOUS:
544				context.spanEndSubst = null;
545
546				if ((checkRule.action & ParserRule.EXCLUDE_MATCH)
547					== ParserRule.EXCLUDE_MATCH)
548				{
549					if(pos != lastOffset)
550					{
551						tokenHandler.handleToken(line,
552							checkRule.token,
553							lastOffset - line.offset,
554							pos - lastOffset,
555							context);
556					}
557
558					tokenHandler.handleToken(line,
559						context.rules.getDefault(),
560						pos - line.offset,pattern.count,
561						context);
562				}
563				else
564				{
565					tokenHandler.handleToken(line,
566						checkRule.token,
567						lastOffset - line.offset,
568						pos - lastOffset + pattern.count,
569						context);
570				}
571
572				break;
573			//}}}
574			default:
575				throw new InternalError("Unhandled major action");
576			}
577
578			// move pos to last character of match sequence
579			pos += (matchedChars - 1);
580			lastOffset = pos + 1;
581
582			// break out of inner for loop to check next char
583		} //}}}
584		//{{{ Handle end of MARK_FOLLOWING
585		else if((context.inRule.action & ParserRule.MARK_FOLLOWING) != 0)
586		{
587			if(pos != lastOffset)
588			{
589				tokenHandler.handleToken(line,
590					context.inRule.token,
591					lastOffset - line.offset,
592					pos - lastOffset,context);
593			}
594
595			lastOffset = pos;
596			context.inRule = null;
597		} //}}}
598
599		return true;
600	} //}}}
601
602	//{{{ handleNoWordBreak() method
603	private void handleNoWordBreak()
604	{
605		if(context.parent != null)
606		{
607			ParserRule rule = context.parent.inRule;
608			if(rule != null && (context.parent.inRule.action
609				& ParserRule.NO_WORD_BREAK) != 0)
610			{
611				if(pos != lastOffset)
612				{
613					tokenHandler.handleToken(line,
614						rule.token,
615						lastOffset - line.offset,
616						pos - lastOffset,context);
617				}
618
619				lastOffset = pos;
620				context = context.parent;
621				keywords = context.rules.getKeywords();
622				context.inRule = null;
623			}
624		}
625	} //}}}
626
627	//{{{ handleTokenWithSpaces() method
628	private void handleTokenWithSpaces(TokenHandler tokenHandler,
629		byte tokenType, int start, int len, LineContext context)
630	{
631		int last = start;
632		int end = start + len;
633
634		for(int i = start; i < end; i++)
635		{
636			if(Character.isWhitespace(line.array[i + line.offset]))
637			{
638				if(last != i)
639				{
640					tokenHandler.handleToken(line,
641					tokenType,last,i - last,context);
642				}
643				tokenHandler.handleToken(line,tokenType,i,1,context);
644				last = i + 1;
645			}
646		}
647
648		if(last != end)
649		{
650			tokenHandler.handleToken(line,tokenType,last,
651				end - last,context);
652		}
653	} //}}}
654
655	//{{{ markKeyword() method
656	private void markKeyword(boolean addRemaining)
657	{
658		int len = pos - lastOffset;
659		if(len == 0)
660			return;
661
662		//{{{ Do digits
663		if(context.rules.getHighlightDigits())
664		{
665			boolean digit = false;
666			boolean mixed = false;
667
668			for(int i = lastOffset; i < pos; i++)
669			{
670				char ch = line.array[i];
671				if(Character.isDigit(ch))
672					digit = true;
673				else
674					mixed = true;
675			}
676
677			if(mixed)
678			{
679				RE digitRE = context.rules.getDigitRegexp();
680
681				// only match against regexp if its not all
682				// digits; if all digits, no point matching
683				if(digit)
684				{ 
685					if(digitRE == null)
686					{
687						// mixed digit/alpha keyword,
688						// and no regexp... don't
689						// highlight as DIGIT
690						digit = false;
691					}
692					else
693					{
694						CharIndexedSegment seg = new CharIndexedSegment(
695							line,false);
696						int oldCount = line.count;
697						int oldOffset = line.offset;
698						line.offset = lastOffset;
699						line.count = len;
700						if(!digitRE.isMatch(seg))
701							digit = false;
702						line.offset = oldOffset;
703						line.count = oldCount;
704					}
705				}
706			}
707
708			if(digit)
709			{
710				tokenHandler.handleToken(line,Token.DIGIT,
711					lastOffset - line.offset,
712					len,context);
713				lastOffset = pos;
714
715				return;
716			}
717		} //}}}
718
719		//{{{ Do keywords
720		if(keywords != null)
721		{
722			byte id = keywords.lookup(line, lastOffset, len);
723
724			if(id != Token.NULL)
725			{
726				tokenHandler.handleToken(line,id,
727					lastOffset - line.offset,
728					len,context);
729				lastOffset = pos;
730				return;
731			}
732		} //}}}
733
734		//{{{ Handle any remaining crud
735		if(addRemaining)
736		{
737			tokenHandler.handleToken(line,context.rules.getDefault(),
738				lastOffset - line.offset,len,context);
739			lastOffset = pos;
740		} //}}}
741	} //}}}
742
743	//{{{ substitute() method
744	private char[] substitute(REMatch match, char[] end)
745	{
746		StringBuffer buf = new StringBuffer();
747		for(int i = 0; i < end.length; i++)
748		{
749			char ch = end[i];
750			if(ch == '$')
751			{
752				if(i == end.length - 1)
753					buf.append(ch);
754				else
755				{
756					char digit = end[i + 1];
757					if(!Character.isDigit(digit))
758						buf.append(ch);
759					else
760					{
761						buf.append(match.toString(
762							digit - '0'));
763						i++;
764					}
765				}
766			}
767			else
768				buf.append(ch);
769		}
770
771		char[] returnValue = new char[buf.length()];
772		buf.getChars(0,buf.length(),returnValue,0);
773		return returnValue;
774	} //}}}
775
776	//}}}
777
778	//{{{ LineContext class
779	/**
780	 * Stores persistent per-line syntax parser state.
781	 */
782	public static class LineContext
783	{
784		private static Hashtable intern = new Hashtable();
785
786		public LineContext parent;
787		public ParserRule inRule;
788		public ParserRuleSet rules;
789		// used for SPAN_REGEXP rules; otherwise null
790		public char[] spanEndSubst;
791
792		//{{{ LineContext constructor
793		public LineContext(ParserRuleSet rs, LineContext lc)
794		{
795			rules = rs;
796			parent = (lc == null ? null : (LineContext)lc.clone());
797		} //}}}
798
799		//{{{ LineContext constructor
800		public LineContext()
801		{
802		} //}}}
803
804		//{{{ intern() method
805		public LineContext intern()
806		{
807			Object obj = intern.get(this);
808			if(obj == null)
809			{
810				intern.put(this,this);
811				return this;
812			}
813			else
814				return (LineContext)obj;
815		} //}}}
816
817		//{{{ hashCode() method
818		public int hashCode()
819		{
820			if(inRule != null)
821				return inRule.hashCode();
822			else if(rules != null)
823				return rules.hashCode();
824			else
825				return 0;
826		} //}}}
827
828		//{{{ equals() method
829		public boolean equals(Object obj)
830		{
831			if(obj instanceof LineContext)
832			{
833				LineContext lc = (LineContext)obj;
834				return lc.inRule == inRule && lc.rules == rules
835					&& MiscUtilities.objectsEqual(parent,lc.parent)
836					&& charArraysEqual(spanEndSubst,lc.spanEndSubst);
837			}
838			else
839				return false;
840		} //}}}
841
842		//{{{ clone() method
843		public Object clone()
844		{
845			LineContext lc = new LineContext();
846			lc.inRule = inRule;
847			lc.rules = rules;
848			lc.parent = (parent == null) ? null : (LineContext) parent.clone();
849			lc.spanEndSubst = spanEndSubst;
850
851			return lc;
852		} //}}}
853
854		//{{{ charArraysEqual() method
855		private boolean charArraysEqual(char[] c1, char[] c2)
856		{
857			if(c1 == null)
858				return (c2 == null);
859			else if(c2 == null)
860				return (c1 == null);
861
862			if(c1.length != c2.length)
863				return false;
864
865			for(int i = 0; i < c1.length; i++)
866			{
867				if(c1[i] != c2[i])
868					return false;
869			}
870
871			return true;
872		} //}}}
873	} //}}}
874}