/*******************************************************************************
* Copyright (c) 2007 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.seam.internal.core.scanner.xml;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.ModelUpdater;
import org.jboss.tools.common.meta.XAttribute;
import org.jboss.tools.common.meta.XModelEntity;
import org.jboss.tools.common.model.XModel;
import org.jboss.tools.common.model.XModelException;
import org.jboss.tools.common.model.XModelObject;
import org.jboss.tools.common.model.filesystems.impl.FolderImpl;
import org.jboss.tools.common.model.plugin.ModelPlugin;
import org.jboss.tools.common.model.project.ext.impl.ValueInfo;
import org.jboss.tools.common.model.util.EclipseJavaUtil;
import org.jboss.tools.common.model.util.EclipseResourceUtil;
import org.jboss.tools.common.model.util.NamespaceMapping;
import org.jboss.tools.jst.web.model.helpers.InnerModelHelper;
import org.jboss.tools.jst.web.model.project.ext.store.XMLValueInfo;
import org.jboss.tools.seam.core.ISeamNamespace;
import org.jboss.tools.seam.core.ISeamProject;
import org.jboss.tools.seam.core.ISeamXmlComponentDeclaration;
import org.jboss.tools.seam.core.SeamCorePlugin;
import org.jboss.tools.seam.core.event.ISeamValue;
import org.jboss.tools.seam.core.event.ISeamValueString;
import org.jboss.tools.seam.internal.core.SeamImport;
import org.jboss.tools.seam.internal.core.SeamProperty;
import org.jboss.tools.seam.internal.core.SeamValueList;
import org.jboss.tools.seam.internal.core.SeamValueMap;
import org.jboss.tools.seam.internal.core.SeamValueMapEntry;
import org.jboss.tools.seam.internal.core.SeamValueString;
import org.jboss.tools.seam.internal.core.SeamXmlComponentDeclaration;
import org.jboss.tools.seam.internal.core.SeamXmlFactory;
import org.jboss.tools.seam.internal.core.scanner.IFileScanner;
import org.jboss.tools.seam.internal.core.scanner.LoadedDeclarations;
import org.jboss.tools.seam.internal.core.scanner.ScannerException;
/**
* @author Viacheslav Kabanovich
*/
public class XMLScanner implements IFileScanner {
private XModel model;
private XModelObject o;
public XMLScanner() {}
/**
* Returns true if file is probable component source -
* has components.xml name or *.component.xml mask.
* @param resource
* @return
*/
public boolean isRelevant(IFile resource) {
return resource.getName().equals("components.xml") || resource.getName().endsWith(".component.xml");
}
/**
* This method should be called only if isRelevant returns true;
* Makes simple check if this java file contains annotation Name.
* @param resource
* @return
*/
public boolean isLikelyComponentSource(IFile f) {
cleanState();
boolean isComponentSource = false;
if(f.isSynchronized(IFile.DEPTH_ZERO) && f.exists()) {
model = InnerModelHelper.createXModel(f.getProject());
if(model != null) {
o = EclipseResourceUtil.getObjectByResource(model, f);
if(o != null) {
isComponentSource = o.getModelEntity().getName().startsWith("FileSeamComponent"); //$NON-NLS-1$
}
}
}
if(!isComponentSource) {
cleanState();
}
return isComponentSource;
}
private void cleanState() {
model = null;
o = null;
}
/**
* Returns list of components
* @param f
* @return
* @throws ScannerException
*/
public LoadedDeclarations parse(IFile f, ISeamProject sp) throws ScannerException {
model = InnerModelHelper.createXModel(f.getProject());
o = EclipseResourceUtil.getObjectByResource(model, f);
return parse(o, f.getFullPath(), sp);
}
static Set<String> COMMON_ATTRIBUTES = new HashSet<String>();
static Set<String> INTERNAL_ATTRIBUTES = new HashSet<String>();
static {
COMMON_ATTRIBUTES.add(ISeamXmlComponentDeclaration.NAME);
COMMON_ATTRIBUTES.add(ISeamXmlComponentDeclaration.CLASS);
COMMON_ATTRIBUTES.add(ISeamXmlComponentDeclaration.SCOPE);
COMMON_ATTRIBUTES.add(ISeamXmlComponentDeclaration.PRECEDENCE);
COMMON_ATTRIBUTES.add(ISeamXmlComponentDeclaration.INSTALLED);
COMMON_ATTRIBUTES.add(ISeamXmlComponentDeclaration.AUTO_CREATE);
COMMON_ATTRIBUTES.add(ISeamXmlComponentDeclaration.JNDI_NAME);
COMMON_ATTRIBUTES.add(ISeamXmlComponentDeclaration.STARTUP);
COMMON_ATTRIBUTES.add(ISeamXmlComponentDeclaration.STARTUP_DEPENDS);
INTERNAL_ATTRIBUTES.add("NAME"); //$NON-NLS-1$
INTERNAL_ATTRIBUTES.add("EXTENSION"); //$NON-NLS-1$
INTERNAL_ATTRIBUTES.add("#comment"); //$NON-NLS-1$
}
public LoadedDeclarations parse(XModelObject o, IPath source, ISeamProject sp) {
NamespaceMapping nm = NamespaceMapping.load(o);
if(o.getParent() instanceof FolderImpl) {
IFile f = ResourcesPlugin.getWorkspace().getRoot().getFile(source);
if(f != null && f.exists()) {
try {
((FolderImpl)o.getParent()).updateChildFile(o, f.getLocation().toFile());
} catch (XModelException e) {
ModelPlugin.getPluginLog().logError(e);
}
if(o.getParent() == null) {
boolean b = isLikelyComponentSource(f);
if(!b) return null;
o = EclipseResourceUtil.getObjectByResource(o.getModel(), f);
if(o == null) return null;
}
}
}
LoadedDeclarations ds = new LoadedDeclarations();
String fileEntity = o.getModelEntity().getName();
if(fileEntity.startsWith("FileSeamComponent") && !fileEntity.startsWith("FileSeamComponents")) { //$NON-NLS-1$
parseComponent(o, source, nm, sp, ds);
return ds;
}
XModelObject[] os = o.getChildren();
for (int i = 0; i < os.length; i++) {
XModelEntity componentEntity = os[i].getModelEntity();
if(componentEntity.getAttribute("class") != null) { //$NON-NLS-1$
parseComponent(os[i], source, nm, sp, ds);
} else if(os[i].getModelEntity().getName().startsWith("SeamFactory")) { //$NON-NLS-1$
SeamXmlFactory factory = new SeamXmlFactory();
factory.setId(os[i]);
factory.setSourcePath(source);
factory.setName(new XMLValueInfo(os[i], ISeamXmlComponentDeclaration.NAME));
factory.setScope(new XMLValueInfo(os[i], ISeamXmlComponentDeclaration.SCOPE));
factory.setValue(new XMLValueInfo(os[i], "value")); //$NON-NLS-1$
factory.setMethod(new XMLValueInfo(os[i], "method")); //$NON-NLS-1$
ds.getFactories().add(factory);
} else if(os[i].getModelEntity().getName().startsWith("SeamImport")) { //$NON-NLS-1$
String v = os[i].getAttributeValue("import");
if(v != null && v.length() > 0) {
SeamImport s = new SeamImport();
s.setSeamPackage(v);
ds.getImports().add(s);
}
}
}
return ds;
}
private void parseComponent(XModelObject c, IPath source, NamespaceMapping nm, ISeamProject sp, LoadedDeclarations ds) {
SeamXmlComponentDeclaration component = new SeamXmlComponentDeclaration();
component.setSourcePath(source);
component.setId(c);
component.setName(new XMLValueInfo(c, getComponentAttribute(c)));
if(isClassAttributeSet(c)) {
component.setClassName(new XMLValueInfo(c, ISeamXmlComponentDeclaration.CLASS));
} else if(c.getModelEntity().getName().equals("FileSeamComponent12")) { //$NON-NLS-1$
component.setClassName(getImpliedClassName(c, source));
} else {
String className = getDefaultClassName(c, nm, sp);
if(className != null) {
component.setClassName(className);
component.setClassNameGuessed(true);
}
}
component.setScope(new XMLValueInfo(c, ISeamXmlComponentDeclaration.SCOPE));
component.setPrecedence(new XMLValueInfo(c, ISeamXmlComponentDeclaration.PRECEDENCE));
component.setInstalled(new XMLValueInfo(c, ISeamXmlComponentDeclaration.INSTALLED));
component.setAutoCreate(new XMLValueInfo(c, ISeamXmlComponentDeclaration.AUTO_CREATE));
component.setJndiName(new XMLValueInfo(c, ISeamXmlComponentDeclaration.JNDI_NAME));
XAttribute[] attributes = c.getModelEntity().getAttributes();
for (int ia = 0; ia < attributes.length; ia++) {
XAttribute a = attributes[ia];
String xml = a.getXMLName();
if(xml == null || xml.length() == 0 || "#comment".equals(xml)) continue; //$NON-NLS-1$
if(COMMON_ATTRIBUTES.contains(xml)) continue;
if(INTERNAL_ATTRIBUTES.contains(xml)) continue;
if(xml.indexOf(":") >= 0) continue; //$NON-NLS-1$
if(xml.startsWith("xmlns")) continue; //$NON-NLS-1$
SeamProperty p = new SeamProperty();
p.setId(xml);
p.setName(new XMLValueInfo(c, "&" + a.getName())); //$NON-NLS-1$
p.setName(toCamelCase(xml, false));
SeamValueString v = new SeamValueString();
v.setId("value"); //$NON-NLS-1$
p.setValue(v);
v.setValue(new XMLValueInfo(c, a.getName()));
component.addProperty(p);
}
XModelObject[] properties = c.getChildren();
for (int j = 0; j < properties.length; j++) {
XModelEntity entity = properties[j].getModelEntity();
SeamProperty p = new SeamProperty();
p.setId(properties[j]);
p.setName(new XMLValueInfo(properties[j], "name")); //$NON-NLS-1$
String name = properties[j].getAttributeValue("name"); //$NON-NLS-1$
if(name == null) {
SeamCorePlugin.getPluginLog().logWarning("Entity " + entity.getName() + " has no 'name' attribute");
continue;
}
String cname = toCamelCase(name, false);
if(!cname.equals(name)) p.setName(cname);
if(entity.getAttribute("value") != null) { //$NON-NLS-1$
//this is simple value;
SeamValueString v = new SeamValueString();
v.setId(properties[j]);
v.setValue(new XMLValueInfo(properties[j], "value")); //$NON-NLS-1$
p.setValue(v);
} else {
XModelObject[] entries = properties[j].getChildren();
if(entity.getChild("SeamListEntry") != null //$NON-NLS-1$
|| "list".equals(entity.getProperty("childrenLoader"))) { //$NON-NLS-1$ //$NON-NLS-2$
//this is list value
SeamValueList vl = new SeamValueList();
vl.setId(properties[j]);
for (int k = 0; k < entries.length; k++) {
SeamValueString v = new SeamValueString();
v.setId(entries[k]);
v.setValue(new XMLValueInfo(entries[k], "value")); //$NON-NLS-1$
vl.addValue(v);
}
p.setValue(vl);
//Sometimes there is an attribute for the same property
SeamProperty pa = (SeamProperty)component.getProperty(cname);
if(pa != null) {
ISeamValue v = pa.getValue();
if(v instanceof ISeamValueString) {
ISeamValueString s = (ISeamValueString)v;
if(s.getValue() != null) {
String ss = s.getValue().getValue();
if(ss != null && ss.length() > 0) {
String[] qs = ss.split(",");
for (int i = 0; i < qs.length; i++) {
SeamValueString vi = new SeamValueString();
vi.setId(pa.getId());
ValueInfo info = new ValueInfo();
info.setValue(qs[i]);
vi.setValue(info);
vl.addValue(vi);
}
}
}
}
}
} else {
//this is map value
SeamValueMap vm = new SeamValueMap();
vm.setId(properties[j]);
for (int k = 0; k < entries.length; k++) {
SeamValueMapEntry e = new SeamValueMapEntry();
e.setId(entries[k]);
SeamValueString key = new SeamValueString();
key.setId(entries[k]);
key.setValue(new XMLValueInfo(entries[k], "key")); //$NON-NLS-1$
e.setKey(key);
SeamValueString value = new SeamValueString();
value.setId(entries[k]);
value.setValue(new XMLValueInfo(entries[k], "value")); //$NON-NLS-1$
e.setValue(value);
vm.addEntry(e);
}
p.setValue(vm);
}
}
component.addProperty(p);
}
ds.getComponents().add(component);
}
private String getComponentAttribute(XModelObject c) {
if(c.getModelEntity().getAttribute("component-name") != null) { //$NON-NLS-1$
return "component-name"; //$NON-NLS-1$
}
return ISeamXmlComponentDeclaration.NAME;
}
private boolean isClassAttributeSet(XModelObject c) {
String value = c.getAttributeValue(ISeamXmlComponentDeclaration.CLASS);
return value != null && value.length() > 0;
}
public static String getImpliedClassName(XModelObject c, IPath path) {
if(EclipseResourceUtil.isJar(path.toString())) {
String suffix = ".component"; //$NON-NLS-1$
String cn = c.getAttributeValue("name"); //$NON-NLS-1$
if(cn.endsWith(suffix)) cn = cn.substring(0, cn.length() - suffix.length());
XModelObject p = c.getParent();
while(p != null && p.getFileType() == XModelObject.FOLDER) {
cn = p.getAttributeValue("name") + "." + cn; //$NON-NLS-1$ //$NON-NLS-2$
p = p.getParent();
}
return cn;
} else {
IFile f = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
if(!f.exists()) return ""; //$NON-NLS-1$
IResource root = EclipseResourceUtil.getJavaSourceRoot(f.getProject());
if(!root.getLocation().isPrefixOf(f.getLocation())) return ""; //$NON-NLS-1$
String relative = f.getLocation().toString().substring(root.getLocation().toString().length());
String suffix = ".component.xml"; //$NON-NLS-1$
if(relative.endsWith(suffix)) {
relative = relative.substring(0, relative.length() - suffix.length());
relative = relative.replace('\\', '/');
if(relative.startsWith("/")) relative = relative.substring(1); //$NON-NLS-1$
return relative.replace('/', '.');
}
}
return null;
}
/**
* This is only limited to supported namespaces provided by seam.
* @param c
* @return
*/
public static String getDefaultClassName(XModelObject c, NamespaceMapping nm, ISeamProject sp) {
String s = c.getModelEntity().getXMLSubPath();
int d = s.indexOf(':');
if(d < 0) return null;
Map<String, Set<ISeamNamespace>> ns = sp == null ? null : sp.getNamespaces();
String namespace = s.substring(0, d);
String tag = s.substring(d + 1);
String packageName = "org.jboss.seam." + namespace;
String name = toCamelCase(tag, true);
if(nm != null) {
String uri = nm.getURIForDefaultNamespace(namespace);
if(uri != null && ns != null && !ns.isEmpty()) {
Set<ISeamNamespace> set = ns.get(uri);
if(set != null && !set.isEmpty()) for (ISeamNamespace n: set) {
String pn = n.getPackage();
if(pn == null || pn.length() == 0) continue;
String cn = pn + "." + name;
packageName = pn;
if(set.size() == 1) break;
IJavaProject jp = EclipseResourceUtil.getJavaProject(sp.getProject());
IType type = null;
try {
type = EclipseJavaUtil.findType(jp, cn);
} catch (JavaModelException e) {
ModelPlugin.getPluginLog().logError(e);
}
if(type != null) {
break;
}
}
}
}
String className = packageName + "." + name; //$NON-NLS-1$
return className;
}
/**
* Copied from org.jboss.seam.init.Initialization
* @param hyphenated
* @param initialUpper
* @return
*/
private static String toCamelCase(String hyphenated, boolean initialUpper)
{
StringTokenizer tokens = new StringTokenizer(hyphenated, "-"); //$NON-NLS-1$
StringBuilder result = new StringBuilder( hyphenated.length() );
String firstToken = tokens.nextToken();
if (initialUpper)
{
result.append( Character.toUpperCase( firstToken.charAt(0) ) )
.append( firstToken.substring(1) );
}
else
{
result.append(firstToken);
}
while ( tokens.hasMoreTokens() )
{
String token = tokens.nextToken();
result.append( Character.toUpperCase( token.charAt(0) ) )
.append( token.substring(1) );
}
return result.toString();
}
}