Java tutorial
/******************************************************************************* * Copyright (c) 2014 Made in Switzerland, Marcel Lutz * All rights reserved. This program and the accompanying materials * are made available under the terms of The MIT License * which accompanies this distribution, and is available at * http://opensource.org/licenses/MIT * * Contributors: * Marcel Lutz - Tapestry 4 module logic ******************************************************************************/ package ch.mlutz.plugins.t4e.tapestry; /** * @author Marcel Lutz * @version 1.0 12.02.2014 */ import static ch.mlutz.plugins.t4e.tools.EclipseTools.extractFileBase; import static ch.mlutz.plugins.t4e.tools.EclipseTools.getPackageFragmentRoots; import java.io.IOException; import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Queue; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.tuple.Pair; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IPackageFragment; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.JavaModelException; import ch.mlutz.plugins.t4e.Activator; import ch.mlutz.plugins.t4e.index.ITapestryModuleChangeListener; import ch.mlutz.plugins.t4e.index.TapestryIndex; import ch.mlutz.plugins.t4e.index.TapestryIndexer; import ch.mlutz.plugins.t4e.log.EclipseLogFactory; import ch.mlutz.plugins.t4e.log.IEclipseLog; import ch.mlutz.plugins.t4e.serializer.EclipseSerializer; import ch.mlutz.plugins.t4e.tapestry.element.ElementType; import ch.mlutz.plugins.t4e.tapestry.element.Service; import ch.mlutz.plugins.t4e.tapestry.element.TapestryElement; import ch.mlutz.plugins.t4e.tapestry.element.TapestryHtmlElement; import ch.mlutz.plugins.t4e.tapestry.parsers.SpecificationParser; import ch.mlutz.plugins.t4e.tools.TapestryTools; public class TapestryModule implements Serializable { /** * */ private static final long serialVersionUID = 4604095727846982073L; /** * the Log */ public static final IEclipseLog log = EclipseLogFactory.create(TapestryModule.class); public static final String WEB_INF_FOLDER_NAME = "WEB-INF"; public static final String WEBAPP_FOLDER_NAME = "webapp"; // application specification private AppSpecification appSpecification; // HiveMind Module Descriptor private transient HiveModuleDescriptor hiveModuleDescriptor; // this could be something like "app" private String moduleId; /* * the folder containing the webapp folder which in turn contains the * webapp files (page and component templates) and possibly the resources * folder containing the hivemodule.xml * the folder might typically be named 'main' */ private transient IContainer webappFolder; public Set<TapestryHtmlElement> getComponents() { return Collections.unmodifiableSet(components); } private Set<TapestryHtmlElement> components; private List<TapestryElement> elements; private Set<TapestryHtmlElement> pages; private Set<Service> services; private transient TapestryIndex tapestryIndexStore; private transient ITapestryModuleChangeListener changeListener; /** * Constructor * * @param appSpecificationFile the modules app specification file * @param changeListener * @throws TapestryException if the project structure is not correct */ public TapestryModule(IFile appSpecificationFile, ITapestryModuleChangeListener changeListener) throws TapestryException { initializeCollections(); validateAppSpecificationFile(appSpecificationFile); appSpecification = new AppSpecification(appSpecificationFile); setChangeListener(changeListener); } private void initializeCollections() { components = new HashSet<TapestryHtmlElement>(); elements = new ArrayList<TapestryElement>(); pages = new HashSet<TapestryHtmlElement>(); services = new HashSet<Service>(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((appSpecification == null) ? 0 : appSpecification.hashCode()); /* result = prime * result + ((moduleId == null) ? 0 : moduleId.hashCode()); result = prime * result + ((webappFolder == null) ? 0 : webappFolder.hashCode()); */ return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof TapestryModule)) { return false; } TapestryModule other = (TapestryModule) obj; if (appSpecification == null) { if (other.appSpecification != null) { return false; } } else if (!appSpecification.equals(other.appSpecification)) { return false; } return true; } public AppSpecification getAppSpecification() { return appSpecification; } public void setAppSpecification(AppSpecification appSpecification) { this.appSpecification = appSpecification; } public HiveModuleDescriptor getHiveModule() { return hiveModuleDescriptor; } /* public void addElementsToTapestryIndex() { for (TapestryHtmlElement htmlElement: components) { for (Pair<IFile, IFile> relation: htmlElement.getRelations()) { getTapestryIndex().addBidiRelation(relation.getLeft(), relation.getRight()); getTapestryIndex().addRelationToCompilationUnit( htmlElement.getHtmlFile(), htmlElement.getJavaCompilationUnit()); } } for (TapestryHtmlElement htmlElement: pages) { for (Pair<IFile, IFile> relation: htmlElement.getRelations()) { getTapestryIndex().addBidiRelation(relation.getLeft(), relation.getRight()); getTapestryIndex().addRelationToCompilationUnit( htmlElement.getHtmlFile(), htmlElement.getJavaCompilationUnit()); } } } */ public void add(TapestryElement element) { TapestryHtmlElement htmlElement; switch (element.getType()) { case COMPONENT: htmlElement = (TapestryHtmlElement) element; components.add(htmlElement); /* for (Pair<IFile, IFile> relation: htmlElement.getRelations()) { getTapestryIndex().addBidiRelation(relation.getLeft(), relation.getRight()); } getTapestryIndex().addRelationToCompilationUnit( htmlElement.getHtmlFile(), htmlElement.getJavaCompilationUnit()); */ break; case PAGE: htmlElement = (TapestryHtmlElement) element; pages.add(htmlElement); /* for (Pair<IFile, IFile> relation: htmlElement.getRelations()) { getTapestryIndex().addBidiRelation(relation.getLeft(), relation.getRight()); } getTapestryIndex().addRelationToCompilationUnit( htmlElement.getHtmlFile(), htmlElement.getJavaCompilationUnit()); */ break; case SERVICE: services.add((Service) element); break; default: elements.add(element); } if (changeListener != null) { changeListener.elementAdded(this, element); } } public void remove(TapestryElement element) { switch (element.getType()) { case COMPONENT: components.remove(element); break; case PAGE: pages.remove(element); break; case SERVICE: services.remove(element); break; default: elements.remove(element); } if (changeListener != null) { changeListener.elementRemoved(this, element); } } /** * Removes all Tapestry elements from this module; methods cascadingly * removes its elements from Tapestry index. */ public void clear() { List<Set<? extends TapestryElement>> listOfElementSets = getListOfElementSets(); for (Set<? extends TapestryElement> set : listOfElementSets) { for (TapestryElement element : set) { this.changeListener.elementRemoved(this, element); } set.clear(); } } /** * Tries to retrieve the hivemodule.xml file. * * @return the IFile handle to hivemodule.xml or null if no suitable file * at the expected location can be found. */ public IFile getHiveModuleFile() { /* * go from webapp folder up to resources/META-inf/hivemodule.xml */ IContainer tapestryRoot = webappFolder.getParent(); assert tapestryRoot != null : "TapestryRoot can't be null."; return tapestryRoot.getFile(new Path("resources/META-INF/hivemodule.xml")); } /** * Retrieves the project this TapestryModule is contained in * * @return the project of this TapestryModule, actually the app * specification's project */ public IProject getProject() { return appSpecification.getAppSpecificationFile().getProject(); } public int getScanAndUpdateWork() { return 100; } /** * Scans app specification for module metadata (mainly page and component * class packages); then scans this module (html and java files) and updates * the central tapestryIndex with file relations; finally scans * hivemodule.xml and collects and stores services in this module * @param monitor * * @throws TapestryException */ public void scanAndUpdateIndex(IProgressMonitor monitor) throws TapestryException { try { // validateAppSpecificationFile(appSpecification.getAppSpecificationFile()); if (!getAppSpecification().update()) { return; } Queue<IResource> resourceQueue = new LinkedList<IResource>(); resourceQueue.add(webappFolder); IResource current = resourceQueue.poll(); while (current != null) { if (current.getType() == IResource.FILE) { if ("html".equals(FilenameUtils.getExtension(current.getName()))) { try { findRelatedUnit((IFile) current); } catch (CoreException e) { log.warn("Couldn't find related unit for " + current.getName(), e); } } } else if (current.getType() == IResource.FOLDER) { // add all members in this folder to queue IResource[] members; try { members = ((IFolder) current).members(); for (IResource member : members) { resourceQueue.add(member); } } catch (CoreException e) { log.warn("Couldn't get members of folder " + current.getName(), e); } } current = resourceQueue.poll(); } } finally { if (monitor != null) { monitor.worked(getScanAndUpdateWork()); } } } protected void validateAppSpecificationFile(IFile appSpecificationFile) throws TapestryException { IContainer webInfFolder = appSpecificationFile.getParent(); if (webInfFolder == null) { throw new TapestryException("Expected " + WEB_INF_FOLDER_NAME + " folder is null"); } if (!WEB_INF_FOLDER_NAME.equals(webInfFolder.getName())) { throw new TapestryException("Expected " + WEB_INF_FOLDER_NAME + " folder has unexpected name '" + webInfFolder.getName() + "'"); } webappFolder = webInfFolder.getParent(); if (webappFolder == null) { throw new TapestryException("Expected " + WEBAPP_FOLDER_NAME + " folder is null"); } if (!WEBAPP_FOLDER_NAME.equals(webappFolder.getName())) { throw new TapestryException("Expected " + WEBAPP_FOLDER_NAME + " folder has unexpected name '" + webappFolder.getName() + "'"); } } public void setHiveModule(HiveModuleDescriptor hiveModule) { this.hiveModuleDescriptor = hiveModule; } public String getModuleId() { return moduleId; } public void setModuleId(String moduleId) { this.moduleId = moduleId; } public IContainer getWebappFolder() { return webappFolder; } public void setWebappFolder(IFolder webappFolder) { this.webappFolder = webappFolder; } @Override public String toString() { return "TapestryModule [appSpecification=" + appSpecification.getAppSpecificationFile() + ", moduleId=" + moduleId + ", webappFolder=" + webappFolder + "]"; } public IFile findRelatedFile(IFile currentFile) throws CoreException { ICompilationUnit compilationUnit = findRelatedUnit(currentFile); if (compilationUnit != null) { try { return (IFile) compilationUnit.getCorrespondingResource().getAdapter(IFile.class); } catch (JavaModelException e) { log.warn("Couldn't get corresponding resource for compilation " + "unit " + compilationUnit.getElementName(), e); } } return null; } private ICompilationUnit findRelatedUnit(IFile currentFile) throws CoreException { if (TapestryTools.isHtmlFileChecked(currentFile)) { return findRelatedUnitForHtml(currentFile); } else if (TapestryTools.isPageSpecification(currentFile)) { return findRelatedUnitForSpecification(currentFile, "page-specification"); } else if (TapestryTools.isComponentSpecification(currentFile)) { return findRelatedUnitForSpecification(currentFile, "component-specification"); } return null; } private ICompilationUnit findRelatedUnitForHtml(IFile currentFile) throws CoreException { // try to find by component specification IFile specificationFile = TapestryTools.findComponentSpecificationforHtmlFile(currentFile); ICompilationUnit compilationUnit; if (specificationFile != null) { compilationUnit = findRelatedUnit(specificationFile); if (compilationUnit != null) { // create component and add to index TapestryHtmlElement component = new TapestryHtmlElement(this, ElementType.COMPONENT, currentFile, specificationFile, compilationUnit); add(component); return compilationUnit; } } // then try to find by page specification specificationFile = TapestryTools.findPageSpecificationforHtmlFile(currentFile); if (specificationFile != null) { compilationUnit = findRelatedUnit(specificationFile); if (compilationUnit != null) { // create page and add to index TapestryHtmlElement page = new TapestryHtmlElement(this, ElementType.PAGE, currentFile, specificationFile, compilationUnit); add(page); return compilationUnit; } } // at last, try directly and assemble fully qualified class name String javaFileName = extractFileBase(currentFile.getName()) + ".java"; // go up until we find a folder named webapp or the project; every folder // on the way up contributes to the packageSuffix StringBuilder sb = new StringBuilder(); IContainer parent = currentFile.getParent(); while (!WEBAPP_FOLDER_NAME.equals(parent.getName().toLowerCase()) && parent.getType() != IResource.PROJECT) { sb.insert(0, parent.getName()); sb.insert(0, '.'); parent = parent.getParent(); } String packageSuffix = sb.toString(); // loop through all page packages Set<String> pageClassPackages = appSpecification.getPageClassPackages(); if (pageClassPackages != null) { compilationUnit = findCompilationUnitInClassPackages(getProject(), pageClassPackages, packageSuffix, javaFileName); // create page and add to index TapestryHtmlElement page = new TapestryHtmlElement(this, ElementType.PAGE, currentFile, specificationFile, compilationUnit); add(page); return compilationUnit; } // resource is null here Set<String> componentClassPackages = appSpecification.getComponentClassPackages(); if (componentClassPackages != null) { compilationUnit = findCompilationUnitInClassPackages(getProject(), componentClassPackages, packageSuffix, javaFileName); // create component and add to index TapestryHtmlElement component = new TapestryHtmlElement(this, ElementType.COMPONENT, currentFile, specificationFile, compilationUnit); add(component); return compilationUnit; } return null; } public ICompilationUnit findRelatedUnitForSpecification(IFile file, String specificationTagName) { // parse specification and extract data SpecificationParser sp; try { sp = new SpecificationParser(specificationTagName); sp.parse(file.getContents()); } catch (UnsupportedEncodingException e) { log.warn("Unsupported encoding while trying to parse file " + file.getName(), e); return null; } catch (CoreException e) { log.warn("CoreException while trying to parse file " + file.getName(), e); return null; } String targetQualifiedClassName = sp.getTargetClass(); if (targetQualifiedClassName == null || "".equals(targetQualifiedClassName)) { log.warn("targetQualifiedClassName in specification " + file.getName() + " is null or empty."); return null; } ICompilationUnit compilationUnit = findCompilationUnit(file.getProject(), targetQualifiedClassName + ".java"); return compilationUnit; } public static ICompilationUnit findCompilationUnit(IProject project, String fullyQualifiedName) { IPackageFragment fragment; ICompilationUnit compilationUnit; IPackageFragmentRoot[] fragmentRoots; try { fragmentRoots = getPackageFragmentRoots(project); } catch (JavaModelException e) { log.warn("Could not get packageFragmentRoots of project " + project.getName(), e); return null; } catch (CoreException e) { log.warn("Could not get packageFragmentRoots of project " + project.getName(), e); return null; } // split fullyQualifiedName into package and simple name // int lastDotIndex= fullyQualifiedName.lastIndexOf('.'); String packageName; String compilationUnitName; Pattern pattern = Pattern.compile("(?:(.*)\\.)?([^.]+\\.[^.]+)"); Matcher matcher = pattern.matcher(fullyQualifiedName); if (matcher.matches()) { packageName = matcher.group(1); if (packageName == null) { packageName = ""; // default package } compilationUnitName = matcher.group(2); } else { return null; } /* if (lastDotIndex != -1) { packageName= fullyQualifiedName.substring(0, lastDotIndex); compilationUnitName= fullyQualifiedName.substring(lastDotIndex + 1, fullyQualifiedName.length()); } else { // no dot found ==> try to retrieve class in default package packageName= ""; compilationUnitName= fullyQualifiedName; } */ for (IPackageFragmentRoot root : fragmentRoots) { try { // only take into account source roots if (root.getKind() != IPackageFragmentRoot.K_SOURCE) { continue; } } catch (JavaModelException e) { log.warn("Could not get kind of packageFragmentRoot " + project.getName(), e); continue; } fragment = root.getPackageFragment(packageName); if (fragment == null) { continue; } /* log.info("Trying package fragment: " + fragment.getPath()); */ compilationUnit = fragment.getCompilationUnit(compilationUnitName); if (compilationUnit != null && compilationUnit.exists()) { return compilationUnit; } } return null; } public static ICompilationUnit findCompilationUnitInClassPackages(IProject project, Iterable<String> classPackages, String packageSuffix, String resourceName) { IPackageFragment fragment; ICompilationUnit compilationUnit; IPackageFragmentRoot[] fragmentRoots; try { fragmentRoots = getPackageFragmentRoots(project); } catch (JavaModelException e) { log.warn("Could not get packageFragmentRoots of project " + project.getName(), e); return null; } catch (CoreException e) { log.warn("Could not get packageFragmentRoots of project " + project.getName(), e); return null; } if (classPackages == null) { // add empty string as classPackage if none supplied classPackages = Arrays.asList(new String[] { "" }); } // loop through class package names for (String packageName : classPackages) { for (IPackageFragmentRoot root : fragmentRoots) { try { // only take into account source roots if (root.getKind() != IPackageFragmentRoot.K_SOURCE) { continue; } } catch (JavaModelException e) { log.warn("Could not get kind of packageFragmentRoot " + project.getName(), e); continue; } fragment = root.getPackageFragment(packageName + packageSuffix); if (fragment == null) { continue; } /* log.info("Trying package fragment: " + fragment.getPath()); */ compilationUnit = fragment.getCompilationUnit(resourceName); if (compilationUnit != null && compilationUnit.exists()) { return compilationUnit; } } } return null; } public TapestryIndex getTapestryIndex() { if (tapestryIndexStore == null) { tapestryIndexStore = Activator.getDefault().getTapestryIndex(); } return tapestryIndexStore; } public TapestryHtmlElement findComponentForSpecification(IFile specification) { for (TapestryHtmlElement component : components) { if (specification.equals(component.getSpecification())) { return component; } } return null; } public TapestryHtmlElement findPageForSpecification(IFile specification) { for (TapestryHtmlElement page : pages) { if (specification.equals(page.getSpecification())) { return page; } } return null; } public TapestryHtmlElement findHtmlElementForHtmlFile(IFile htmlFile) { for (TapestryHtmlElement page : pages) { if (htmlFile.equals(page.getHtmlFile())) { return page; } } for (TapestryHtmlElement component : components) { if (htmlFile.equals(component.getHtmlFile())) { return component; } } return null; } // serialization private void writeObject(java.io.ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); EclipseSerializer.serializeResource(stream, webappFolder); } private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot(); webappFolder = EclipseSerializer.deserializeResource(stream, workspaceRoot, IContainer.class); for (TapestryHtmlElement component : components) { component.setParent(this); } for (TapestryHtmlElement page : pages) { page.setParent(this); } setChangeListener(Activator.getDefault().getTapestryIndexer()); } /** * @return the changeListener */ public ITapestryModuleChangeListener getChangeListener() { return changeListener; } /** * @param changeListener the changeListener to set */ public void setChangeListener(ITapestryModuleChangeListener changeListener) { if (this.changeListener == changeListener) { return; } List<Set<? extends TapestryElement>> listOfElementSets = getListOfElementSets(); // remove all elements from old change listener if (this.changeListener != null) { for (Set<? extends TapestryElement> set : listOfElementSets) { for (TapestryElement element : set) { this.changeListener.elementRemoved(this, element); } } } // add all elements to new change listener if (changeListener != null) { for (Set<? extends TapestryElement> set : listOfElementSets) { for (TapestryElement element : set) { changeListener.elementAdded(this, element); } } } this.changeListener = changeListener; } private List<Set<? extends TapestryElement>> getListOfElementSets() { List<Set<? extends TapestryElement>> result = new ArrayList<Set<? extends TapestryElement>>(); result.add(components); result.add(pages); result.add(services); return result; } }