blob: 8134f86b7d311ecdb0399d15e4dfdf08851d704c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 Oracle Corporation.
* 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:
* Gerry Kessler/Oracle - initial API and implementation
*
********************************************************************************/
package org.eclipse.jst.jsf.taglibprocessing.internal.provisional.attributevalues;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jst.jsf.context.resolver.structureddocument.internal.provisional.IStructuredDocumentContextResolverFactory;
import org.eclipse.jst.jsf.context.resolver.structureddocument.internal.provisional.IWorkspaceContextResolver;
import org.eclipse.jst.jsf.metadataprocessors.internal.provisional.features.IPossibleValues;
import org.eclipse.jst.jsf.metadataprocessors.internal.provisional.features.IValidValues;
import org.eclipse.jst.jsf.metadataprocessors.internal.provisional.features.PossibleValue;
import org.eclipse.jst.jsf.metadataprocessors.internal.provisional.features.ValidationMessage;
/**
* Provides possible values and validates attribute values that should be fully qualified Java types.
* A type can be verified against muliple "valid-interfaces" and/or a "valid-superclass" from meta-data.
* Code checks to ensure the class can be instantiated (i.e. not abstract, anonymous or inner class)
* Search is scoped to within the current project only.
*
* (Until https://bugs.eclipse.org/bugs/show_bug.cgi?id=142044 is fixed, only the first found will be used)
*
* @author Gerry Kessler - Oracle
*
*/
public class JavaClassType extends ObjectType implements IPossibleValues, IValidValues{
public static final String POSSIBLE_VALUES_INTERFACES_PROP_NAME = "valid-interfaces"; //$NON-NLS-1$
public static final String POSSIBLE_VALUES_SUPERCLASS_PROP_NAME = "valid-superclass"; //$NON-NLS-1$
private List validationMsgs;
/* (non-Javadoc)
* @see org.eclipse.jst.jsf.metadataprocessors.internal.provisional.features.IPossibleValues#getPossibleValues()
*/
public List getPossibleValues() {
List results = getTypes();
if (results != null && !results.isEmpty()){
Set vals = new HashSet(results.size());
Set checkedTypes = new HashSet();
for (Iterator it = results.iterator();it.hasNext();){
SearchMatch match = (SearchMatch)it.next();
IType res = (IType)match.getElement();
addValidSubClasses(res, vals, checkedTypes);
}
return createPossibleValues(vals);
}
return new ArrayList(0);
}
private List createPossibleValues(Set vals) {
List list = new ArrayList(vals.size());
Iterator it = vals.iterator();
while(it.hasNext()){
IJavaElement elem = (IJavaElement)it.next();
list.add(createPossibleValue(elem));
}
return list;
}
private void addValidSubClasses(IType res, Set vals, Set checkedTypes) {
try {
//check to see if we have already checked the hiearchy
if (checkedTypes.contains(res))
return;
//should we add itself?
if (isInnerOrAnonymousClass(res))
return;
if (!isAbstractClass(res))
vals.add(res); //since it is a set, dupes will not be added
ITypeHierarchy hierarchy = res.newTypeHierarchy(getJavaProject(), null);
IType[] subclasses = hierarchy.getSubclasses(res);
checkedTypes.add(res);
for (int i=0;i<subclasses.length;i++){
addValidSubClasses(subclasses[i], vals, checkedTypes);
}
} catch (JavaModelException e) {
//ignore
}
}
private IWorkspaceContextResolver getWorkspaceContextResolver(){
if (getStructuredDocumentContext() == null)
return null;
return IStructuredDocumentContextResolverFactory.INSTANCE.getWorkspaceContextResolver(getStructuredDocumentContext());
}
private List getTypes(){
IJavaProject jp = getJavaProject();
if (jp == null)
return null;
List elems = new ArrayList();
elems.addAll(getInterfaces(jp));
IType sc = getSuperClass(jp);
if (sc != null)
elems.add(sc);
if (elems.size() > 0){
SearchRequestor requestor = new Searcher();
SearchEngine engine = new SearchEngine();
IJavaSearchScope scope = SearchEngine.createJavaSearchScope(new IJavaElement[]{jp}, IJavaSearchScope.SOURCES | IJavaSearchScope.APPLICATION_LIBRARIES);
SearchPattern combined = SearchPattern.createPattern((IJavaElement)elems.get(0), IJavaSearchConstants.IMPLEMENTORS, 0);
// Until this bug is fixed, stub it out... only the first interface/superclass will be used.
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=142044
// for(int i=1;i<elems.size();i++){
// final SearchPattern other = SearchPattern.createPattern((IJavaElement)elems.get(i), IJavaSearchConstants.IMPLEMENTORS, 0);
// combined = SearchPattern.createAndPattern(combined, other);
// }
try {
engine.search(combined, new SearchParticipant[] {SearchEngine.getDefaultSearchParticipant()}, scope, requestor, null);
} catch (CoreException e) {
//ignore
}
return ((Searcher)requestor).getResults();
}
return new ArrayList(0);
}
private IJavaProject getJavaProject() {
IWorkspaceContextResolver resolver = getWorkspaceContextResolver();
if (resolver != null){
IProject proj = resolver.getProject();
if (proj != null)
return JavaCore.create(proj);
}
return null;
}
private List getInterfaces(IJavaProject jp) {
List ret = new ArrayList();
List propVals = getInterfaceNames();
for (Iterator it = propVals.iterator();it.hasNext();){
String propVal = (String)it.next();
IType interfase = null;
try {
interfase = findType(jp, propVal);
if (interfase != null){
ret.add(interfase);
}
} catch (JavaModelException e) {
// suppress and fall-through to return empty list
}
}
return ret;
}
private IType getSuperClass(IJavaProject jp){
IType superclass = null;
try {
String sc = getSuperClassName();
if (sc != null && !sc.trim().equals("")){ //$NON-NLS-1$
superclass = findType(jp, sc );
if (superclass != null){
return superclass;
}
}
} catch (JavaModelException e) {
//ignore
}
return null;
}
private PossibleValue createPossibleValue(IJavaElement val) {
return new PossibleValue(((IType)val).getFullyQualifiedName());
}
/* (non-Javadoc)
* @see org.eclipse.jst.jsf.metadataprocessors.internal.provisional.features.IValidValues#getValidationMessages()
*/
public List getValidationMessages() {
if (validationMsgs == null){
validationMsgs = new ArrayList();
}
return validationMsgs;
}
/* (non-Javadoc)
* @see org.eclipse.jst.jsf.metadataprocessors.internal.provisional.features.IValidValues#isValidValue(java.lang.String)
*/
public boolean isValidValue(String value) {
if (value == null || value.trim().equals("")){ //$NON-NLS-1$
getValidationMessages().add(new ValidationMessage(Messages.JavaClassType_invalid_type));
return false;
}
IJavaProject jp = getJavaProject();
if (jp == null)
return false;
//first verify that the value specified is a resolvable type
IType type = getTypeForValue(jp, value);
if (type != null){
//ensure that it is not abstract or anonymous
if (!isInnerOrAnonymousClass(type) && !isAbstractClass(type)){
//now verify that it meets the criteria
List results = getTypes();
if (!results.isEmpty()){
for (Iterator it = results.iterator();it.hasNext();){
SearchMatch match = (SearchMatch)it.next();
IType res = (IType)match.getElement();
if (!isInnerOrAnonymousClass(res) ){
//if this is the class, then optimize to reduce expense of creating hierarchy
if (!isAbstractClass(type) && (res.getFullyQualifiedName().equals(value)) )
return true;
//check to see if value is in hierarchy
try {
ITypeHierarchy hierarchy = res.newTypeHierarchy(jp, null);
if (hierarchy.contains(type))
return true;
} catch (JavaModelException e) {
//ignore
}
}
}
}
}
}
addNewValidationMessage(Messages.JavaClassType_not_found);
return false;
}
private IType getTypeForValue(IJavaProject jp, String value) {
try {
return findType(jp, value);
} catch (JavaModelException e) {
// suppress and fall through to return null
}
return null;
}
/**
* @return String value of {@link POSSIBLE_VALUES_SUPERCLASS_PROP_NAME}
*/
protected String getSuperClassName(){
return getTraitValueAsString(POSSIBLE_VALUES_SUPERCLASS_PROP_NAME);
// return CMAnnotationHelper.getCMAttributePropertyValue(getMetaDataContext().getBundleId(), getMetaDataContext().getUri(),
// getMetaDataContext().getElementName(), getMetaDataContext().getAttributeName(),
// POSSIBLE_VALUES_SUPERCLASS_PROP_NAME);
}
/**
* @return List of values from {@link POSSIBLE_VALUES_INTERFACES_PROP_NAME}
*/
protected List getInterfaceNames(){
return getTraitValueAsListOfStrings(POSSIBLE_VALUES_INTERFACES_PROP_NAME);
// return CMAnnotationHelper.getCMAttributePropertyValues(getMetaDataContext().getBundleId(), getMetaDataContext().getUri(),
// getMetaDataContext().getElementName(), getMetaDataContext().getAttributeName(),
// POSSIBLE_VALUES_INTERFACES_PROP_NAME);
}
//need to refactor below as this as also in Enumeration
protected void addNewValidationMessage(String defaultMsg) {
String msg = getCMValidationMessage();
if (msg == null || msg.equals("")) //$NON-NLS-1$
msg = defaultMsg;
String code = getValidationCode();
int severity = getValidationSeverity();
ValidationMessage val = new ValidationMessage(msg, code, severity);
getValidationMessages().add(val);
}
/**
* @return validation message from meta-data. Can be null.
*/
protected String getCMValidationMessage() {
return getTraitValueAsString(IValidValues.VALID_VALUES_MESSAGE_PROP_NAME);
}
/**
* @return validation severity as int from meta-data. IStatus.WARNING is default.
*/
protected int getValidationSeverity() {
String val = getTraitValueAsString(IValidValues.VALID_VALUES_SEVERITY_PROP_NAME);
if (val == null)
return IStatus.WARNING;
int severity = Integer.valueOf(val).intValue();
return severity;
}
/**
* @return validation code as String from meta-data. Can be null.
*/
protected String getValidationCode() {
return getTraitValueAsString(IValidValues.VALID_VALUES_CODE_PROP_NAME);
}
private boolean isInnerOrAnonymousClass(IType res) {
try {
if (res.isClass() && (res.isAnonymous() ||
((res.getFlags() & Flags.AccPrivate) == Flags.AccPrivate ) ||
res.getFullyQualifiedName().indexOf("$") > 0)) //must be better way to discover if it is an inner class
return true;
} catch (JavaModelException e) {
//ignore
}
return false;
}
private boolean isAbstractClass(IType res) {
try {
if (res.isClass() && (res.getFlags() & Flags.AccAbstract) == Flags.AccAbstract)
return true;
} catch (JavaModelException e) {
//ignore
}
return false;
}
private class Searcher extends SearchRequestor{
private List results = new ArrayList();
public void acceptSearchMatch(SearchMatch match) throws CoreException {
results.add(match);
}
public List getResults(){
return results;
}
}
///////////////// ///////////////////////////////////////////////////////////////////////
//remainder of this class copied from org.eclipse.jdt.internal.corext.util.JavaCoreUtil //
//TODO: find public version of this functionality //
//////////////////////////////////////////////////////////////////////////////////////////
private IType findType(IJavaProject jproject, String fullyQualifiedName) throws JavaModelException {
//workaround for bug 22883
IType type= jproject.findType(fullyQualifiedName);
if (type != null)
return type;
IPackageFragmentRoot[] roots= jproject.getPackageFragmentRoots();
for (int i= 0; i < roots.length; i++) {
IPackageFragmentRoot root= roots[i];
type= findType(root, fullyQualifiedName);
if (type != null && type.exists())
return type;
}
return null;
}
private IType findType(IPackageFragmentRoot root, String fullyQualifiedName) throws JavaModelException{
IJavaElement[] children= root.getChildren();
for (int i= 0; i < children.length; i++) {
IJavaElement element= children[i];
if (element.getElementType() == IJavaElement.PACKAGE_FRAGMENT){
IPackageFragment pack= (IPackageFragment)element;
if (! fullyQualifiedName.startsWith(pack.getElementName()))
continue;
IType type= findType(pack, fullyQualifiedName);
if (type != null && type.exists())
return type;
}
}
return null;
}
private IType findType(IPackageFragment pack, String fullyQualifiedName) throws JavaModelException{
ICompilationUnit[] cus= pack.getCompilationUnits();
for (int i= 0; i < cus.length; i++) {
ICompilationUnit unit= cus[i];
IType type= findType(unit, fullyQualifiedName);
if (type != null && type.exists())
return type;
}
return null;
}
private IType findType(ICompilationUnit cu, String fullyQualifiedName) throws JavaModelException{
IType[] types= cu.getAllTypes();
for (int i= 0; i < types.length; i++) {
IType type= types[i];
if (getFullyQualifiedName(type).equals(fullyQualifiedName))
return type;
}
return null;
}
private String getFullyQualifiedName(IType type) {
try {
if (type.isBinary() && !type.isAnonymous()) {
IType declaringType= type.getDeclaringType();
if (declaringType != null) {
return getFullyQualifiedName(declaringType) + '.' + type.getElementName();
}
}
} catch (JavaModelException e) {
// ignore
}
return type.getFullyQualifiedName('.');
}
////////////////////////////////////////////////////////////////////////
}