/*******************************************************************************
* Copyright (c) 2009 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.common.el.core.resolver;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.Region;
import org.jboss.tools.common.el.core.ELReference;
/**
* EL context
* @author Alexey Kazakov
*/
public class ELContextImpl extends SimpleELContext {
public static final List<Var> EMPTY = Collections.<Var>emptyList();
protected List<Var> allVars = new ArrayList<Var>();
protected ELReference[] elReferences;
protected List<ELReference> elReferenceSet;
/*
* (non-Javadoc)
* @see org.jboss.tools.common.el.core.resolver.ELContext#getVars()
*/
@Override
public synchronized Var[] getVars() {
List<Var> vars = getVarsAsList();
return vars.toArray(new Var[vars.size()]);
}
@Override
public synchronized List<Var> getVarsAsList() {
List<Var> external = getExternalVars();
if(external.isEmpty()) {
return allVars;
} else if(allVars.isEmpty()) {
return external;
}
ArrayList<Var> result = new ArrayList<Var>();
result.addAll(allVars);
result.addAll(external);
return result;
}
/**
* Returns list of vars that are not defined withing current page,
* for instance, by <ui:param> on a page that includes this page.
*
* @return
*/
public List<Var> getExternalVars() {
return EMPTY;
}
/**
* Adds new Var to the context
* @param region
* @param vars
*/
public synchronized void addVar(Region region, Var var) {
var.setRegion(region);
allVars.add(var);
}
/*
* (non-Javadoc)
* @see org.jboss.tools.common.el.core.resolver.ELContext#getVars(int)
*/
@Override
public synchronized Var[] getVars(int offset) {
List<Var> vars = getVarsAsList(offset);
return vars.toArray(new Var[vars.size()]);
}
@Override
public synchronized List<Var> getVarsAsList(int offset) {
if(offset < 0) {
return getVarsAsList();
}
List<Var> external = getExternalVars();
if(allVars.isEmpty()) {
return external;
}
List<Var> result = new ArrayList<Var>();
for (Var var : allVars) {
Region region = var.getRegion();
if(offset>=region.getOffset() && offset<=region.getOffset() + region.getLength()) {
result.add(var);
}
}
if(!external.isEmpty()) {
result.addAll(external);
}
return result;
}
/**
* @return the allVars
*/
public List<Var> getAllVars() {
return allVars;
}
/**
* @param allVars the allVars to set
*/
public void setAllVars(List<Var> allVars) {
this.allVars = allVars;
}
/**
* @see org.jboss.tools.jst.web.kb.IXmlContext#getELReferences()
*
* Implementation returns sorted array of EL references.
*/
@Override
public synchronized ELReference[] getELReferences() {
if(elReferences==null) {
if(elReferenceSet==null || elReferenceSet.isEmpty()) {
return elReferences = EMPTY_ARRAY;
}
elReferences = elReferenceSet.toArray(new ELReference[0]);
sortELReferences();
}
return elReferences;
}
public synchronized void addELReference(ELReference reference) {
if(elReferenceSet==null) {
elReferenceSet = new ArrayList<ELReference>();
}
elReferenceSet.add(reference);
elReferences = null;
}
/**
* @see org.jboss.tools.common.el.core.resolver.SimpleELContext#getELReference(int)
*
* Implementation uses binary search in sorted array of EL references.
*/
@Override
public synchronized ELReference getELReference(int offset) {
getELReferences();
if(elReferences.length == 0) {
return null;
} else if(elReferences.length == 1) {
ELReference ref = elReferences[0];
if(ref.getStartPosition()<=offset && (ref.getStartPosition() + ref.getLength()>offset)) {
return ref;
}
} else {
int left = 0;
ELReference ref = elReferences[left];
if(ref.getStartPosition() > offset) return null;
if(ref.getStartPosition() + ref.getLength() >= offset) return ref;
int right = elReferences.length - 1;
ref = elReferences[right];
if(ref.getStartPosition() + ref.getLength() < offset) return null;
if(ref.getStartPosition() <= offset) return ref;
while(right - left > 1) {
int middle = (right + left) / 2;
ref = elReferences[middle];
if(ref.getStartPosition()<=offset && (ref.getStartPosition() + ref.getLength()>offset)) {
return ref;
} else if(ref.getStartPosition() > offset) {
right = middle;
} else {
left = middle;
}
}
return null;
}
return null;
}
/**
* Implementation first uses binary search in sorted array of EL references
* for the left and right borders of the region, then checks and collects
* all EL references out of the defined range.
*/
@Override
public synchronized Collection<ELReference> getELReferences(IRegion region) {
List<ELReference> references = new ArrayList<ELReference>();
if(elReferenceSet == null || elReferenceSet.isEmpty()) {
return references;
}
getELReferences();
int min = getMin(region.getOffset());
int max = getMax(region.getOffset() + region.getLength());
if(min >= 0 && max >= min) {
for (int i = min; i <= max; i++) {
ELReference ref = elReferences[i];
if(region.getOffset() + region.getLength() >= ref.getStartPosition() && region.getOffset() <= ref.getStartPosition() + ref.getLength()) {
references.add(ref);
}
}
}
return references;
}
/**
* Returns index of an EL reference such that all previous references lie to the left of offset
* and all subsequent references either contain the offset or lie to the right of it.
* The EL reference defined by the returned index, may either contain the offset or not.
*
* This method is called by getELReferences(IRegion) with offset of the left border of the region.
*
* @param offset
* @return
*/
private int getMin(int offset) {
if(elReferences.length == 0) {
return -1;
} else if(elReferences.length == 1) {
ELReference ref = elReferences[0];
if(ref.getStartPosition()<=offset && (ref.getStartPosition() + ref.getLength()>offset)) {
return 0;
}
} else {
int left = 0;
ELReference ref = elReferences[left];
if(ref.getStartPosition() + ref.getLength() >= offset) return left;
int right = elReferences.length - 1;
ref = elReferences[right];
if(ref.getStartPosition() + ref.getLength() < offset) return -1;
if(ref.getStartPosition() <= offset) return right;
while(right - left > 1) {
int middle = (right + left) / 2;
ref = elReferences[middle];
if(ref.getStartPosition()<=offset && (ref.getStartPosition() + ref.getLength()>offset)) {
return middle;
} else if(ref.getStartPosition() + ref.getLength() < offset) {
left = middle + 1;
} else {
right = middle;
}
}
return left;
}
return -1;
}
/**
* Returns index of an EL reference such that all subsequent references lie to the rigth of offset
* and all previous references either contain the offset or lie to the left of it.
* The EL reference defined by the returned index, may either contain the offset or not.
*
* This method is called by getELReferences(IRegion) with offset of the right border of the region.
*
* @param offset
* @return
*/
private int getMax(int offset) {
if(elReferences.length == 0) {
return -1;
} else if(elReferences.length == 1) {
ELReference ref = elReferences[0];
if(ref.getStartPosition() <= offset && (ref.getStartPosition() + ref.getLength()>offset)) {
return 0;
}
} else {
int left = 0;
ELReference ref = elReferences[left];
if(ref.getStartPosition() > offset) return -1;
if(ref.getStartPosition() + ref.getLength() >= offset) return left;
int right = elReferences.length - 1;
ref = elReferences[right];
if(ref.getStartPosition() <= offset) return right;
while(right - left > 1) {
int middle = (right + left) / 2;
ref = elReferences[middle];
if(ref.getStartPosition()<=offset && (ref.getStartPosition() + ref.getLength()>offset)) {
return middle;
} else if(ref.getStartPosition() > offset) {
right = middle - 1;
} else {
left = middle;
}
}
return right;
}
return -1;
}
/**
* Implementation sorts and removes duplicated EL references.
*/
private void sortELReferences() {
if(elReferenceSet == null || elReferenceSet.size() < 2) return;
Arrays.sort(elReferences, new ReferencesComparator());
List<ELReference> newReferencesSet = new ArrayList<ELReference>();
newReferencesSet.add(elReferences[0]);
ELReference last = null;
for (int i = 0; i < elReferences.length; i++) {
ELReference next = elReferences[i];
if(last != null && last.getStartPosition() + last.getLength() >= next.getStartPosition()) {
continue;
}
newReferencesSet.add(next);
last = next;
}
elReferenceSet = newReferencesSet;
if(elReferenceSet.size() < elReferences.length) {
elReferences = null;
getELReferences();
}
}
static class ReferencesComparator implements Comparator<ELReference> {
@Override
public int compare(ELReference o1, ELReference o2) {
if(o1.getStartPosition() != o2.getStartPosition()) {
return o1.getStartPosition() - o2.getStartPosition();
}
return o2.getStartPosition() + o2.getLength() - o1.getStartPosition() - o1.getLength();
}
}
}