Java tutorial
/* * Copyright (c) 2010-2012 Research In Motion Limited. All rights reserved. * * This program and the accompanying materials are made available * under the terms of the Eclipse Public License, Version 1.0, * which accompanies this distribution and is available at * * http://www.eclipse.org/legal/epl-v10.html * */ package net.rim.ejde.internal.packaging; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; import net.rim.ejde.internal.core.ContextManager; import net.rim.ejde.internal.core.IConstants; import net.rim.ejde.internal.core.IRIMMarker; import net.rim.ejde.internal.launching.DeploymentHelper; import net.rim.ejde.internal.model.BlackBerryProject; import net.rim.ejde.internal.model.BlackBerryProjectCoreNature; import net.rim.ejde.internal.model.BlackBerryProperties; import net.rim.ejde.internal.signing.SignatureToolLaunchAction; import net.rim.ejde.internal.ui.consoles.PackagingConsole; import net.rim.ejde.internal.util.ImportUtils; import net.rim.ejde.internal.util.Messages; import net.rim.ejde.internal.util.PackagingUtils; import net.rim.ejde.internal.util.ProjectUtils; import net.rim.ejde.internal.util.ResourceBuilderUtils; import net.rim.ejde.internal.util.StatusFactory; import net.rim.ejde.internal.validation.DiagnosticFactory; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.eclipse.core.internal.resources.ResourceException; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.resources.IWorkspaceRunnable; import org.eclipse.core.resources.IncrementalProjectBuilder; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.QualifiedName; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.osgi.util.NLS; /** * This class represents the job for packaging BlackBerry projects with or without deployment to the simulator. This job should be * run after eclipse building is done. Please use {@link PackagingJobWrapper} to run the packaging job. * */ public abstract class PackagingJob implements IWorkspaceRunnable { static private final Logger _log = Logger.getLogger(PackagingJob.class); final static public QualifiedName BUILT_BY_JAVA_BUILDER_FLAG_QUALIFIED_NAME = new QualifiedName( ContextManager.PLUGIN_ID, "BuiltByEclipseBuilders"); //$NON-NLS-1$ final static private String TRUE_STRING = "true"; //$NON-NLS-1$ final static private String FALSE_STRING = "false"; //$NON-NLS-1$ private Set<BlackBerryProject> _projects; private int _signingFlag; // sign flag // always sign final static public int SIGN_FORCE = 0; // sign only if packaging is needed final static public int SIGN_IF_NECESSARY = 1; // never sign final static public int SIGN_NO = 2; // sign only if protected APIs are used final static public int SIGN_IF_PROTECTED_API_USED = 3; /** * Constructs a PackagingJob instance. * * @param projects * projects need to be packaged. */ public PackagingJob(Set<BlackBerryProject> projects) { _projects = projects; _signingFlag = SIGN_NO; } /** * Constructs a PackagingJob instance. * * @param projects * @param signingFlag */ public PackagingJob(Set<BlackBerryProject> projects, int signingFlag) { _projects = projects; _signingFlag = signingFlag; } /** * Mark the given <code>project</code> as need to be built. * * @param project * @param needBuild * <code>true</code> mark the given <code>project</code> as need build, otherwise, mark the given * <code>project</code> as not need build. */ static synchronized public void setBuiltByJavaBuilders(IProject project, boolean needBuild) { try { _log.trace("Project " + project.getName() + " is marked as" //$NON-NLS-1$ //$NON-NLS-2$ + (needBuild ? " need RAPC." : " not need RAPC.")); //$NON-NLS-1$ //$NON-NLS-2$ String newBuildFlag = needBuild ? TRUE_STRING : FALSE_STRING; String oldBuildFlag = project.getPersistentProperty(BUILT_BY_JAVA_BUILDER_FLAG_QUALIFIED_NAME); if ((oldBuildFlag == null) || !oldBuildFlag.equals(newBuildFlag)) { project.setPersistentProperty(BUILT_BY_JAVA_BUILDER_FLAG_QUALIFIED_NAME, newBuildFlag); } } catch (CoreException e) { _log.error(e); } } /** * Check if the given <code>project</code> needs to be built. * * @param project * @return */ static synchronized private boolean builtByJavaBuilder(IProject project) { try { String value = project.getProject().getPersistentProperty(BUILT_BY_JAVA_BUILDER_FLAG_QUALIFIED_NAME); if ((value == null) || value.equals(TRUE_STRING)) { return true; } } catch (CoreException e) { _log.error(e); return true; } return false; } /** * Gets projects which need to be packaged; * * @return */ public Set<BlackBerryProject> getProjects() { return _projects; } @Override public void run(IProgressMonitor monitor) throws CoreException { // remove the code signing error ResourceBuilderUtils.cleanProblemMarkers(ResourcesPlugin.getWorkspace().getRoot(), new String[] { IRIMMarker.SIGNATURE_TOOL_PROBLEM_MARKER }, IResource.DEPTH_ONE); // open the packaging console PackagingConsole.getInstance().activate(); LinkedHashSet<BlackBerryProject> projectSet = ProjectUtils.getProjectsByBuildOrder(_projects); monitor.beginTask(IConstants.EMPTY_STRING, projectSet.size() * 10); monitor.subTask(Messages.PackagingJob_Name); boolean needSign = false; // collect projects which need to be signed LinkedHashSet<BlackBerryProject> projectsNeedSigning = new LinkedHashSet<BlackBerryProject>(); // collect projects whose dependent projects need to be signed LinkedHashSet<BlackBerryProject> projectsDependencyNeedSigning = new LinkedHashSet<BlackBerryProject>(); // collect projects which are packaged successfully LinkedHashSet<BlackBerryProject> succesfullyPackagedProjects = new LinkedHashSet<BlackBerryProject>(); for (BlackBerryProject bbProject : projectSet) { // 1. run java build on the project if (!isBuildAutomaticallyOn()) { try { bbProject.getProject().build(IncrementalProjectBuilder.AUTO_BUILD, new SubProgressMonitor(monitor, 1)); } catch (CoreException e) { _log.error(e); } } monitor.worked(3); // 2. package the project if (!needPackaging(bbProject)) { if (needGenerateALXFile(bbProject)) { PackagingManager.generateALXForProject(bbProject); } } else { // remove the package problems ResourceBuilderUtils.cleanProblemMarkers(bbProject.getProject(), new String[] { IRIMMarker.PACKAGING_PROBLEM }, IResource.DEPTH_INFINITE); try { PackagingManager.packageProject(bbProject); if (!needSign) { needSign = true; } } catch (CoreException e) { _log.error(e.getMessage()); try { ResourceBuilderUtils.createProblemMarker( e.getStatus().getCode() == DiagnosticFactory.CREATE_FOLDER_ERR_ID ? bbProject.getMetaFileHandler() : bbProject.getProject(), IRIMMarker.PACKAGING_PROBLEM, e.getMessage(), -1, IMarker.SEVERITY_ERROR); } catch (Exception e1) { _log.error(e1.getMessage()); } } PackagingJob.setBuiltByJavaBuilders(bbProject.getProject(), false); } monitor.worked(4); // 3. run post-build command runPostBuild(bbProject); monitor.worked(1); // 4. check if the project needs to be signed or not if (!hasPackagingProblems(bbProject.getProject())) { succesfullyPackagedProjects.add(bbProject); if (PackagingUtils.isSigningNeeded(bbProject)) { projectsNeedSigning.add(bbProject); } else { if (PackagingUtils.isSigningNeededForDependency(bbProject)) { projectsDependencyNeedSigning.add(bbProject); } else { // if a project and its dependent projects do not need to be signed, copy the cod files to the web folder // copy the cod files of dependency projects to the deployment folders copyDependencyDeploymentFiles(bbProject); // copy files from "Standard" to "Web" copyToWebDeploymentFolder(bbProject); } } } monitor.worked(2); if (monitor.isCanceled()) { monitor.done(); return; } } // Code signing switch (_signingFlag) { case SIGN_FORCE: { if (!succesfullyPackagedProjects.isEmpty()) { signCodFile(succesfullyPackagedProjects, monitor); } break; } case SIGN_IF_PROTECTED_API_USED: { if (!projectsNeedSigning.isEmpty()) { signCodFile(projectsNeedSigning, monitor); for (BlackBerryProject project : projectsDependencyNeedSigning) { // copy the cod files of dependency projects to the deployment folders copyDependencyDeploymentFiles(project); // copy files from "Standard" to "Web" copyToWebDeploymentFolder(project); } } break; } case SIGN_IF_NECESSARY: { if (needSign) { if (!projectsNeedSigning.isEmpty()) { signCodFile(projectsNeedSigning, monitor); for (BlackBerryProject project : projectsDependencyNeedSigning) { // copy the cod files of dependency projects to the deployment folders copyDependencyDeploymentFiles(project); // copy files from "Standard" to "Web" copyToWebDeploymentFolder(project); } } } break; } } monitor.done(); return; } abstract protected void runPostBuild(BlackBerryProject properties); private void signCodFile(Set<BlackBerryProject> projectSet, IProgressMonitor monitor) throws CoreException { boolean successful = SignatureToolLaunchAction.signCodFiles(projectSet, monitor); if (successful) { for (BlackBerryProject bbProject : projectSet) { // copy the cod files of dependency projects to the deployment folders copyDependencyDeploymentFiles(bbProject); // copy files from "Standard" to "Web" copyToWebDeploymentFolder(bbProject); } } } /** * Copies the deployment files of the dependent projects to the corresponding deployment folders. * * @throws CoreException */ private void copyDependencyDeploymentFiles(BlackBerryProject bbProject) throws CoreException { IPath standardSrcFolderPath, standardDstFolderPath; String outputFileName; IPath absoluteStandardSrcFolderPath, absoluteStandardDstFolderPath; for (BlackBerryProject dependentProj : ProjectUtils.getAllReferencedProjects(bbProject)) { // get the standard deployment folder of the dependent project standardDstFolderPath = new Path(PackagingUtils.getRelativeStandardOutputFolder(dependentProj)); // get the standard corresponding deployment folder of the main project absoluteStandardDstFolderPath = bbProject.getProject().getLocation().append(standardDstFolderPath); // make sure the destination folder is created try { ImportUtils.createFolders(bbProject.getProject(), standardDstFolderPath, IResource.DERIVED); } catch (CoreException e) { throw new ResourceException(DiagnosticFactory.CREATE_FOLDER_ERR_ID, bbProject.getMetaFileHandler().getProjectRelativePath(), NLS.bind(Messages.PackagingManager_PACKAGING_CANNOT_CREATE_FOLDER_MSG, standardDstFolderPath), e); } // copy deployment files in standard folder standardSrcFolderPath = new Path(PackagingUtils.getRelativeStandardOutputFolder(dependentProj)); absoluteStandardSrcFolderPath = dependentProj.getProject().getLocation().append(standardSrcFolderPath); outputFileName = dependentProj.getProperties()._packaging.getOutputFileName(); File[] outputFiles = absoluteStandardSrcFolderPath.toFile() .listFiles(new DeploymentFileNameFilter(outputFileName)); _log.trace("Dependent project " + dependentProj.getElementName() + " copy folder: " + standardSrcFolderPath + " --> " + bbProject.getElementName() + " / " + standardDstFolderPath); for (File file : outputFiles) { if (file.exists()) { DeploymentHelper.executeCopy(file, absoluteStandardDstFolderPath.append(file.getName()).toFile()); } } // refresh the standard folder IFolder folder = bbProject.getProject().getFolder(standardDstFolderPath); folder.refreshLocal(IResource.DEPTH_ONE, new NullProgressMonitor()); } } private void copyToWebDeploymentFolder(BlackBerryProject bbProject) throws CoreException { String outputRootFolder = bbProject.getProperties()._packaging.getOutputFolder(); IPath outputRootFolderPath = new Path(outputRootFolder); IPath standardOutputFolderPath = outputRootFolderPath .append(PackagingUtils.getStandardDeploymentFolderName()); IPath webOutputFolderPath = outputRootFolderPath.append(PackagingUtils.getWebDeploymentFolderName()); IFolder srcFolder = bbProject.getProject().getFolder(standardOutputFolderPath); srcFolder.accept(new CopyResourceVistor(bbProject, standardOutputFolderPath, webOutputFolderPath)); // Refresh to show new resources IResource outputFolder = bbProject.getProject().findMember(webOutputFolderPath); outputFolder.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor()); } class CopyResourceVistor implements IResourceVisitor { BlackBerryProject _bbProject; IPath _srcRootPath; IPath _destRootPath; IFolder _srcRootFolder; IFolder _destRootFolder; IProject _project; public CopyResourceVistor(BlackBerryProject bbProject, IPath srcRootPath, IPath destRootPath) { _bbProject = bbProject; _project = _bbProject.getProject(); _srcRootPath = srcRootPath; _destRootPath = destRootPath; _srcRootFolder = _project.getFolder(_srcRootPath); _destRootFolder = _project.getFolder(_destRootPath); } @Override public boolean visit(IResource resource) throws CoreException { if (resource instanceof IFolder) { if (!resource.equals(_srcRootFolder)) { IPath subPath = resource.getProjectRelativePath() .removeFirstSegments(_srcRootPath.segmentCount()); IPath destPath = _destRootPath.append(subPath); folderCopy(_bbProject, resource.getProjectRelativePath(), destPath); } else { return true; } } return false; } } /** * Copy files form the <code>srcFolderRelativePath</code> to the <code>destFolderRelativePath</code> in project * <code>bbProject</code>. If there is any cod file, un-zip it if it contains any sibling cod files and then copy the sibling * files to the <code>destFolder</code> . * * @param bbProject * @param srcFolderRelativePath * @param destFolderRelativePath * @throws CoreException */ private void folderCopy(BlackBerryProject bbProject, IPath srcFolderRelativePath, IPath destFolderRelativePath) throws CoreException { try { ImportUtils.createFolders(bbProject.getProject(), destFolderRelativePath, IResource.DERIVED); } catch (CoreException e) { throw new ResourceException(DiagnosticFactory.CREATE_FOLDER_ERR_ID, bbProject.getMetaFileHandler().getProjectRelativePath(), NLS.bind(Messages.PackagingManager_PACKAGING_CANNOT_CREATE_FOLDER_MSG, destFolderRelativePath), e); } IPath srcFolderPath = bbProject.getProject().getLocation().append(srcFolderRelativePath); IPath destFolderPath = bbProject.getProject().getLocation().append(destFolderRelativePath); // handle the cod files File[] codFiles = srcFolderPath.toFile().listFiles(new CodFileFilter()); if (codFiles == null) { return; } _log.trace("Project " + bbProject.getElementName() + " folder copy: " + srcFolderRelativePath + " --> " + destFolderRelativePath); for (int i = 0; i < codFiles.length; i++) { copySiblingCod(new Path(codFiles[i].getAbsolutePath()), destFolderPath); } // IPath codFilePath = standardOutputFolderPath.append( bbProject.getProperties()._packaging.getOutputFileName() // + IConstants.COD_FILE_EXTENSION_WITH_DOT ); // copy other files to the web deployment folder File[] files = srcFolderPath.toFile() .listFiles(new DeploymentFileNameFilter(IConstants.EMPTY_STRING, true)); IPath destinationFilePath; String fileName; for (File file : files) { fileName = file.getName(); destinationFilePath = destFolderPath.append(fileName); DeploymentHelper.executeCopy(file, destinationFilePath.toFile()); } } class FolderFilter implements FileFilter { @Override public boolean accept(File pathname) { if (pathname.isDirectory() || pathname.exists()) { return true; } return false; } } class DeploymentFileNameFilter implements FilenameFilter { String _outputFileName; boolean _excludeCodFile; public DeploymentFileNameFilter(String outputFileName) { _outputFileName = outputFileName; } public DeploymentFileNameFilter(String outputFileName, boolean excludeCod) { _outputFileName = outputFileName; _excludeCodFile = excludeCod; } @Override public boolean accept(File dir, String name) { if (!StringUtils.isBlank(_outputFileName) && !name.startsWith(_outputFileName)) { return false; } if (_excludeCodFile) { if (!name.endsWith(IConstants.COD_FILE_EXTENSION_WITH_DOT)) { return true; } } else { return true; } return false; } } class CodFileFilter implements FilenameFilter { @Override public boolean accept(File dir, String name) { if (name.endsWith(IConstants.COD_FILE_EXTENSION_WITH_DOT)) { return true; } return false; } } /** * If the cod file represented by the given <code>codFilePath</code> contains sibling cod file, un-zip it and copy all sibling * cod files to the <code>destinationFolderPath</code>. If the cod file is a single cod file, just copy it to the * <code>destinationFolderPath</code>. * * @param codFilePath * @param destinationFolderPath * @throws CoreException */ private void copySiblingCod(IPath codFilePath, IPath destinationFolderPath) throws CoreException { boolean hasSiblingCod = false; File codFile = codFilePath.toFile(); try { JarFile zipFile = new JarFile(codFile); Enumeration<JarEntry> entries = zipFile.entries(); if (entries.hasMoreElements()) { hasSiblingCod = true; JarEntry entry; for (; entries.hasMoreElements();) { entry = entries.nextElement(); if (entry.isDirectory()) { // this should not happen continue; } InputStream is = zipFile.getInputStream(entry); File outputFile = destinationFolderPath.append(entry.getName()).toFile(); PackagingManager.copyInputStream(is, new BufferedOutputStream(new FileOutputStream(outputFile))); } } else { hasSiblingCod = false; } } catch (IOException e) { if (codFile.exists()) { // if the cod file does not contain any sibling file, we get IOException hasSiblingCod = false; } else { _log.error(e); } } finally { if (!hasSiblingCod) { // if the cod file is a single cod file, copy it to the destination DeploymentHelper.executeCopy(codFile, destinationFolderPath.append(codFile.getName()).toFile()); } } } private boolean isBuildAutomaticallyOn() { return ResourcesPlugin.getWorkspace().getDescription().isAutoBuilding(); } private boolean needPackaging(BlackBerryProject bbproj) throws CoreException { // check if the project has any critical problem if (ProjectUtils.hasCriticalProblems(bbproj.getProject())) { return false; } List<BlackBerryProject> dependencyPrjoects = ProjectUtils.getAllReferencedProjects(bbproj); // check if the project's dependency projects have any critical problem, it does not make sense to package a project while // its dependency projects can not be packaged. if (hasProblemOnDependency(dependencyPrjoects)) { return false; } // check if the project has any packaging problem if (hasPackagingProblems(bbproj.getProject())) { return true; } // check if the project has been built by the java builder if (PackagingJob.builtByJavaBuilder(bbproj.getProject())) { return true; } // check if the project has been updated if (hasBeenUpdated(bbproj)) { return true; } // check if the project's dependency projects have been updated if (hasBeenUpdated(bbproj, dependencyPrjoects)) { return true; } return false; } private boolean needGenerateALXFile(BlackBerryProject project) { BlackBerryProperties properties = project.getProperties(); if (!properties._packaging.getGenerateALXFile().booleanValue()) { return false; } String filename = properties._packaging.getOutputFileName() + IConstants.ALX_FILE_EXTENSION_WITH_DOT; IPath alxFilePath = new Path( PackagingUtils.getRelativeAlxFileOutputFolder(project) + IPath.SEPARATOR + filename); IResource alxIFile = project.getProject().findMember(alxFilePath); return (alxIFile == null) || (!alxIFile.exists()); } /** * Checks if any of the BlackBerry project in the given <code>bbProjects</code> has been update after the last time the * <code>mainProject</code> was packaged. * * @param mainProject * @param bbProjects * @return */ private boolean hasBeenUpdated(BlackBerryProject mainProject, List<BlackBerryProject> bbProjects) { // we only check the cod file in the standard output folder IPath refFilePath = mainProject.getProject().getLocation() .append(PackagingUtils.getRelativeStandardOutputFolder(mainProject)); refFilePath = refFilePath.append(new Path(mainProject.getProperties()._packaging.getOutputFileName() + IConstants.COD_FILE_EXTENSION_WITH_DOT)); File refFile = refFilePath.toFile(); if (!refFile.exists()) { return true; } IPath dependencySampleFilePath = null; File dependencyCodFile = null; for (BlackBerryProject bbProject : bbProjects) { dependencySampleFilePath = bbProject.getProject().getLocation() .append(PackagingUtils.getRelativeStandardOutputFolder(bbProject)); dependencySampleFilePath = dependencySampleFilePath .append(new Path(bbProject.getProperties()._packaging.getOutputFileName() + IConstants.DEBUG_FILE_EXTENSION_WITH_DOT)); dependencyCodFile = dependencySampleFilePath.toFile(); if (dependencyCodFile.lastModified() > refFile.lastModified()) { return true; } } return false; } private boolean hasBeenUpdated(BlackBerryProject bbproj) throws CoreException { IPath refFilePath = bbproj.getProject().getLocation() .append(PackagingUtils.getRelativeStandardOutputFolder(bbproj)); refFilePath = refFilePath.append(new Path( bbproj.getProperties()._packaging.getOutputFileName() + IConstants.DEBUG_FILE_EXTENSION_WITH_DOT)); File refFile = refFilePath.toFile(); if (!refFile.exists()) { return true; } IFile iDescriptorFile = bbproj.getProject().getFile(BlackBerryProject.METAFILE); File descriptorFile = iDescriptorFile.getLocation().toFile(); // check the time stamp of the project descriptor file if (!descriptorFile.exists() && bbproj.getProject().hasNature(BlackBerryProjectCoreNature.NATURE_ID)) { // TODO not support java project now throw new CoreException(StatusFactory.createErrorStatus("Project does not have the descriptor file.")); } if (descriptorFile.lastModified() > refFile.lastModified()) { return true; } //Find the custom JAD file in the project root String custjad = PackagingUtils.getCustomJadFile(bbproj); if (custjad != null) { File jadFile = new File(custjad); if (jadFile.lastModified() > refFile.lastModified()) { return true; } } //Find the custom rapc file in the project root String custrapc = PackagingUtils.getCustomJadFile(bbproj); if (custrapc != null) { File rapcFile = new File(custrapc); if (rapcFile.lastModified() > refFile.lastModified()) { return true; } } // check if the output folders and their children have been updated Set<File> outputFolders = ImportUtils.getOutputFolderSet(bbproj); for (File folder : outputFolders) { if (ProjectUtils.hasFolderBeenUpdated(folder, refFile.lastModified())) { return true; } } return false; } /** * Check if the projects in the given <code>project</code> have any packaging problems. * * @param project * @return */ private boolean hasPackagingProblems(IProject project) { if (project == null) { return false; } try { IMarker[] markers = project.findMarkers(IRIMMarker.PACKAGING_PROBLEM, true, IResource.DEPTH_INFINITE); for (IMarker marker : markers) { Integer severity = (Integer) marker.getAttribute(IMarker.SEVERITY); if ((severity != null) && (severity.intValue() >= IMarker.SEVERITY_ERROR)) { return true; } } return false; } catch (CoreException e) { _log.error(e); return true; } } private boolean hasProblemOnDependency(List<BlackBerryProject> dependencyPrjoects) throws CoreException { IProject iProject = null; for (BlackBerryProject jProject : dependencyPrjoects) { iProject = jProject.getProject(); if (ProjectUtils.hasCriticalProblems(iProject)) { return true; } } return false; } }