content: /**
   *  Copyright (c) 2013-2016 Angelo ZERR.
   *  All rights reserved. This program and the accompanying materials
   *  are made available under the terms of the Eclipse Public License v1.0
   *  which accompanies this distribution, and is available at
   *  http://www.eclipse.org/legal/epl-v10.html
   *
   *  Contributors:
   *  Angelo Zerr <angelo.zerr@gmail.com> - initial API and implementation
   */
  package tern.eclipse.jface.text;
  
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.List;
  
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.jface.text.BadLocationException;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.jface.text.TextAttribute;
  import org.eclipse.jface.text.rules.IToken;
  import org.eclipse.jface.text.rules.ITokenScanner;
  import org.eclipse.jface.text.rules.Token;
  
  import tern.ITernProject;
  import tern.TernException;
  import tern.server.protocol.highlight.ITernHighlightCollector;
  import tern.server.protocol.highlight.TernHighlightQuery;
  
  /**
   * Abstract class which implement {@link ITokenScanner} which uses the tern
   * plugin https://github.com/katspaugh/tj-mode to collect JavaScript code
   * tokens.
   * 
   * This scanner uses acorn parser which is able to parse ECMAScript 5/6.
   *
   */
  public abstract class TernTokenScanner implements ITokenScanner {
  
  	private static final Comparator<TernToken> TERN_TOKEN_COMPARATOR = new Comparator<TernToken>() {
  		@Override
  		public int compare(TernToken t1, TernToken t2) {
  			return (int) (t1.end - t2.end);
  		}
  	};
  
  	private final List<TernToken> tokens = new ArrayList<TernToken>();
  	private TernToken current;
  	private int index;
  
  	@Override
  	public void setRange(IDocument document, int offset, int length) {
  		this.tokens.clear();
  		this.index = 0;
  
  		// Call tern-highlight
  		TernTokenList collector = new TernTokenList(offset, length);
  		try {
  			highlight(document, collector);
  		} catch (Exception e) {
  			e.printStackTrace();
  		}
  
  		// sort
  		Collections.sort(collector, TERN_TOKEN_COMPARATOR);
  
  		// fill with empty token
  		TernToken lastToken = null;
  		for (TernToken ternToken : collector) {
  			if (lastToken != null) {
  				tokens.add(new TernToken("", lastToken.end, ternToken.start, null));
  			}
  			tokens.add(ternToken);
  			lastToken = ternToken;
  		}
  		if (lastToken != null) {
  			tokens.add(new TernToken("", lastToken.end, (long) (length + offset), null));
  		} else {
  			tokens.add(new TernToken("", (long) offset, (long) (length + offset), null));
  		}
  	}
  
  	private void highlight(IDocument document, TernTokenList collector)
  			throws CoreException, BadLocationException, IOException, TernException {
  		ITernProject ternProject = getTernProject(document);
  		if (ternProject != null) {
  			String text = document.get(collector.getOffset(), collector.getLength());
  			TernHighlightQuery query = new TernHighlightQuery(text);
  			ternProject.request(query, null, collector);
  		}
  	}
  
  	@Override
  	public IToken nextToken() {
  		if (index < tokens.size()) {
  			current = tokens.get(index);
  			index++;
  			return current;
  		}
  		return Token.EOF;
  	}
  
  	@Override
  	public int getTokenOffset() {
  		return current.getOffset();
  	}
  
  	@Override
  	public int getTokenLength() {
  		return current.getLength();
  	}
  
  	private class TernToken extends Token {
  		public final String type;
  		public final Long start;
  		public final Long end;
  
  		public TernToken(String type, Long start, Long end, TextAttribute attribute) {
  			super(attribute);
  			this.type = type;
  			this.start = start;
  			this.end = end;
  		}
  
  		public int getLength() {
  			if (end.intValue() - start.intValue() < 0) {
  				System.err.println("Arghhh!!!");
  			}
  			return end.intValue() - start.intValue();
  		}
  
  		public int getOffset() {
  			return start.intValue();
  		}
  	}
  
  	private class TernTokenList extends ArrayList<TernToken>implements ITernHighlightCollector {
  
  		private final int offset;
  		private final int length;
  
  		public TernTokenList(int offset, int length) {
  			this.offset = offset;
  			this.length = length;
  		}
  
  		@Override
  		public void higlight(String type, Long start, Long end) {
  			TextAttribute attribute = getTextAttribute(type);
  			super.add(new TernToken(type, start + offset, end + offset, attribute));
  		}
  
  		public int getOffset() {
  			return offset;
  		}
  
  		public int getLength() {
  			return length;
  		}
  	}
  
  	protected abstract TextAttribute getTextAttribute(String type);
  
  	protected abstract ITernProject getTernProject(IDocument document) throws CoreException;
  }
  