blob: 66d1537a0c9ca39d5212ddf40fc6e5bd9148016a [file] [log] [blame]
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2011 Oracle and/or its affiliates. All rights reserved.
*
* Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*
* Contributor(s):
*
* Portions Copyrighted 2011 Sun Microsystems, Inc.
*/
package org.netbeans.modules.jackpot30.backend.impl;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarOutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipEntry;
import org.apache.lucene.analysis.KeywordAnalyzer;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.FSDirectory;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.GlobalPathRegistry;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
import org.netbeans.api.java.queries.SourceForBinaryQuery.Result2;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.ui.OpenProjects;
import org.netbeans.api.sendopts.CommandException;
import org.netbeans.modules.jackpot30.backend.impl.spi.IndexAccessor;
import org.netbeans.modules.jackpot30.backend.impl.spi.StatisticsGenerator;
import org.netbeans.modules.java.source.indexing.JavaIndex;
import org.netbeans.modules.parsing.impl.indexing.CacheFolder;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.netbeans.spi.project.support.ant.PropertyUtils;
import org.netbeans.spi.sendopts.Env;
import org.netbeans.spi.sendopts.Option;
import org.netbeans.spi.sendopts.OptionProcessor;
import org.openide.LifecycleManager;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
import org.openide.util.lookup.ServiceProvider;
/**
*
* @author lahvac
*/
@ServiceProvider(service=OptionProcessor.class)
public class OptionProcessorImpl extends OptionProcessor {
private static final Logger LOG = Logger.getLogger(OptionProcessorImpl.class.getName());
private final Option CATEGORY_ID = Option.requiredArgument(Option.NO_SHORT_NAME, "category-id");
private final Option CATEGORY_NAME = Option.requiredArgument(Option.NO_SHORT_NAME, "category-name");
private final Option CATEGORY_PROJECTS = Option.additionalArguments(Option.NO_SHORT_NAME, "category-projects");
private final Option CATEGORY_ROOT_DIR = Option.requiredArgument(Option.NO_SHORT_NAME, "category-root-dir");
private final Option CACHE_TARGET = Option.requiredArgument(Option.NO_SHORT_NAME, "cache-target");
private final Option INFO = Option.requiredArgument(Option.NO_SHORT_NAME, "info");
private final Set<Option> OPTIONS = new HashSet<Option>(Arrays.asList(CATEGORY_ID, CATEGORY_NAME, CATEGORY_PROJECTS, CATEGORY_ROOT_DIR, CACHE_TARGET, INFO));
private final boolean STORE_CLASSPATH = true;
@Override
protected Set<Option> getOptions() {
return OPTIONS;
}
@Override
protected void process(Env env, Map<Option, String[]> optionValues) throws CommandException {
String categoryId = null;
String categoryName = null;
if (optionValues.containsKey(CATEGORY_ID)) {
categoryId = optionValues.get(CATEGORY_ID)[0];
}
if (optionValues.containsKey(CATEGORY_NAME)) {
categoryName = optionValues.get(CATEGORY_NAME)[0];
}
if (optionValues.containsKey(CATEGORY_PROJECTS)) {
if (categoryId == null) {
env.getErrorStream().println("Error: no category-id specified!");
return;
}
if (categoryName == null) {
env.getErrorStream().println("Warning: no category-name specified.");
return;
}
}
String cacheTarget = optionValues.get(CACHE_TARGET)[0];
File cache = FileUtil.normalizeFile(new File(cacheTarget));
cache.getParentFile().mkdirs();
if (categoryId == null) {
env.getErrorStream().println("Error: no category-id specified!");
return;
}
File baseDirFile = new File(optionValues.get(CATEGORY_ROOT_DIR)[0]);
FileObject baseDir = FileUtil.toFileObject(baseDirFile);
IndexWriter w = null;
FileObject cacheFolder = CacheFolder.getCacheFolder();
FileObject cacheTemp = cacheFolder.getFileObject("index");
Map<String, String> classpath;
Map<FileObject, String> extraJars = new HashMap<FileObject, String>();
try {
if (cacheTemp != null) cacheTemp.delete();
cacheTemp = cacheFolder.createFolder("index");
w = new IndexWriter(FSDirectory.open(FileUtil.toFile(cacheTemp)), new KeywordAnalyzer(), IndexWriter.MaxFieldLength.UNLIMITED);
IndexAccessor.current = new IndexAccessor(w, baseDir);
Set<FileObject> roots = getRoots(optionValues.get(CATEGORY_PROJECTS), env);
classpath = indexProjects(roots, extraJars, env);
} catch (InterruptedException ex) {
LOG.log(Level.FINE, null, ex);
throw (CommandException) new CommandException(0).initCause(ex);
} catch (IOException ex) {
LOG.log(Level.FINE, null, ex);
throw (CommandException) new CommandException(0).initCause(ex);
} finally {
if (w != null) {
try {
w.optimize(true);
w.close(true);
} catch (CorruptIndexException ex) {
Exceptions.printStackTrace(ex);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}
Map<String, Long> statistics = Collections.emptyMap();
IndexReader r = null;
try {
r = IndexReader.open(FSDirectory.open(FileUtil.toFile(cacheTemp)), true);
statistics = StatisticsGenerator.generateStatistics(r);
} catch (CorruptIndexException ex) {
Exceptions.printStackTrace(ex);
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
} finally {
if (r != null) {
try {
r.close();
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
}
cacheTemp.refresh();
JarOutputStream out = null;
InputStream segments = null;
try {
out = new JarOutputStream(new FileOutputStream(cache));
pack(out, cacheTemp, null, "index", new StringBuilder(categoryId));
segments = cacheFolder.getFileObject("segments").getInputStream();
Properties in = new Properties();
in.load(segments);
segments.close();//XXX: should be in finally!
String baseDirPath = baseDirFile.toURI().toString();
Properties outSegments = new Properties();
for (String segment : in.stringPropertyNames()) {
String url = in.getProperty(segment);
String rel;
if (url.startsWith(baseDirPath)) rel = "rel:/" + url.substring(baseDirPath.length());
else if (url.startsWith("jar:" + baseDirPath)) rel = "jar:rel:/" + url.substring(4 + baseDirPath.length());
else rel = url;
outSegments.setProperty(segment, rel);
}
out.putNextEntry(new ZipEntry(categoryId + "/segments"));
outSegments.store(out, "");
out.putNextEntry(new ZipEntry(categoryId + "/info"));
out.write("{\n".getBytes("UTF-8"));
out.write(("\"displayName\": \"" + categoryName + "\"").getBytes("UTF-8"));
if (optionValues.containsKey(INFO)) {
for (String infoValue : optionValues.get(INFO)[0].split(";")) {
int eqSign = infoValue.indexOf('=');
if (eqSign == (-1)) {
LOG.log(Level.INFO, "No ''='' sign in: {0}", infoValue);
continue;
}
out.write((",\n\"" + infoValue.substring(0, eqSign) + "\": \"" + infoValue.substring(eqSign + 1) + "\"").getBytes("UTF-8"));
}
}
out.write(",\n \"statistics\" : {\n".getBytes("UTF-8"));
boolean wasEntry = false;
for (Entry<String, Long> e : statistics.entrySet()) {
if (wasEntry) out.write(", \n".getBytes("UTF-8"));
out.write(("\"" + e.getKey() + "\" : " + e.getValue()).getBytes("UTF-8"));
wasEntry = true;
}
out.write("\n}\n".getBytes("UTF-8"));
out.write("\n}\n".getBytes("UTF-8"));
if (STORE_CLASSPATH) {
out.putNextEntry(new ZipEntry(categoryId + "/classpath"));
for (Entry<String, String> e : classpath.entrySet()) {
out.write((e.getKey() + "=" + e.getValue() + "\n").getBytes("UTF-8"));
}
for (Entry<FileObject, String> ej : extraJars.entrySet()) {
out.putNextEntry(new ZipEntry(categoryId + "/" + ej.getValue()));
InputStream jarIn = ej.getKey().getInputStream();
try {
FileUtil.copy(jarIn, out);
} finally {
jarIn.close();
}
}
}
for (FileObject s : cacheFolder.getChildren()) {
if (!s.isFolder() || !s.getNameExt().startsWith("s") || s.getChildren().length == 0) continue;
JarOutputStream local = null;
try {
out.putNextEntry(new ZipEntry(categoryId + "/" + s.getNameExt()));
local = new JarOutputStream(out);
pack(local, s, baseDir.toURI().toString(), "", new StringBuilder(""));
} finally {
if (local != null) {
local.finish();
}
}
}
} catch (IOException ex) {
LOG.log(Level.FINE, null, ex);
throw (CommandException) new CommandException(0).initCause(ex);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException ex) {
throw (CommandException) new CommandException(0).initCause(ex);
}
}
if (segments != null) {
try {
segments.close();
} catch (IOException ex) {
throw (CommandException) new CommandException(0).initCause(ex);
}
}
}
LifecycleManager.getDefault().exit();
}
private Set<FileObject> getRoots(String[] projects, Env env) {
Set<FileObject> sourceRoots = new HashSet<FileObject>(projects.length * 4 / 3 + 1);
for (String p : projects) {
try {
LOG.log(Level.FINE, "Processing project specified as: {0}", p);
File f = PropertyUtils.resolveFile(env.getCurrentDirectory(), p);
File normalized = FileUtil.normalizeFile(f);
FileObject prjFO = FileUtil.toFileObject(normalized);
if (prjFO == null) {
env.getErrorStream().println("Project location cannot be found: " + p);
continue;
}
if (!prjFO.isFolder()) {
env.getErrorStream().println("Project specified as: " + p + " does not point to a directory (" + FileUtil.getFileDisplayName(prjFO));
continue;
}
Project prj = ProjectManager.getDefault().findProject(prjFO);
if (prj == null) {
env.getErrorStream().println("Project specified as: " + p + " does not resolve to a project (" + FileUtil.getFileDisplayName(prjFO));
continue;
}
LOG.log(Level.FINE, "project resolved: {0} ({1})", new Object[] {ProjectUtils.getInformation(prj), prj.getClass()});
SourceGroup[] javaSG = ProjectUtils.getSources(prj).getSourceGroups("java");
if (javaSG.length == 0) {
env.getErrorStream().println("Project specified as: " + p + " does not define a java source groups (" + FileUtil.getFileDisplayName(prjFO));
continue;
}
for (SourceGroup sg : javaSG) {
LOG.log(Level.FINE, "Found source group: {0}", FileUtil.getFileDisplayName(sg.getRootFolder()));
sourceRoots.add(sg.getRootFolder());
}
} catch (ThreadDeath td) {
throw td;
} catch (Throwable ex) {
LOG.log(Level.FINE, null, ex);
Exceptions.printStackTrace(ex);
env.getErrorStream().println("Cannot work with project specified as: " + p + " (" + ex.getLocalizedMessage() + ")");
}
}
return sourceRoots;
}
private Map<String, String> indexProjects(Set<FileObject> sourceRoots, Map<FileObject, String> extraJars, Env env) throws IOException, InterruptedException {
if (sourceRoots.isEmpty()) {
env.getErrorStream().println("Error: There is nothing to index!");
return Collections.emptyMap();
} else {
//XXX: to start up the project systems and RepositoryUpdater:
((Runnable) OpenProjects.getDefault().openProjects()).run();
org.netbeans.api.project.ui.OpenProjects.getDefault().getOpenProjects();
ClassPath source = ClassPathSupport.createClassPath(sourceRoots.toArray(new FileObject[0]));
LOG.log(Level.FINE, "Registering as source path: {0}", source.toString());
GlobalPathRegistry.getDefault().register(ClassPath.SOURCE, new ClassPath[] {source});
SourceUtils.waitScanFinished();
Map<String, String> classpath = new HashMap<String, String>();
if (STORE_CLASSPATH) {
long extraJarCounter = 0;
for (FileObject sourceRoot : sourceRoots) {
StringBuilder cp = new StringBuilder();
ClassPath sourceCP = ClassPath.getClassPath(sourceRoot, ClassPath.SOURCE);
if (sourceCP != null) {
for (ClassPath.Entry e : sourceCP.entries()) {
cp.append(CacheFolder.getDataFolder(e.getURL()).getNameExt());
cp.append(":");
}
}
ClassPath compileCP = ClassPath.getClassPath(sourceRoot, ClassPath.COMPILE);
if (compileCP != null) {
for (ClassPath.Entry e : compileCP.entries()) {
Result2 sourceMapping = SourceForBinaryQuery.findSourceRoots2(e.getURL());
if (sourceMapping.preferSources() && /*XXX:*/ sourceMapping.getRoots().length > 0) {
for (FileObject sr : sourceMapping.getRoots()) {
cp.append(CacheFolder.getDataFolder(sr.toURL()).getNameExt());
cp.append(":");
}
} else {
FileObject root = e.getRoot();
FileObject jar = root != null ? FileUtil.getArchiveFile(root) : null;
if (jar != null) root = jar;
if (root != null && root.isData()) { //XXX: class folders
String rootId = extraJars.get(root);
if (rootId == null) {
extraJars.put(root, rootId = "ej" + extraJarCounter++ + ".jar");
}
cp.append(rootId);
cp.append(":");
}
}
}
}
if (cp.length() > 0)
cp.deleteCharAt(cp.length() - 1);
classpath.put(CacheFolder.getDataFolder(sourceRoot.toURL()).getNameExt(), cp.toString());
}
}
GlobalPathRegistry.getDefault().unregister(ClassPath.SOURCE, new ClassPath[] {source});
return classpath;
}
}
private void pack(JarOutputStream target, FileObject index, String baseURL, String name, StringBuilder relPath) throws IOException {
int len = relPath.length();
boolean first = relPath.length() == 0;
if (!first) relPath.append("/");
relPath.append(name);
boolean data = index.isData();
if (relPath.length() > 0) {
target.putNextEntry(new ZipEntry(relPath.toString() + (data ? "" : "/")));
}
if (data) {
InputStream in = index.getInputStream();
try {
if (baseURL != null && (("java/" + JavaIndex.VERSION + "/checksums.properties").contentEquals(relPath) || ("java/" + JavaIndex.VERSION + "/fqn2files.properties").contentEquals(relPath))) {
fixAbsolutePath(in, target, baseURL, "rel:/");
} else {
FileUtil.copy(in, target);
}
} finally {
in.close();
}
}
for (FileObject c : index.getChildren()) {
if (first && c.getNameExt().equals("segments")) continue;
pack(target, c, baseURL, c.getNameExt(), relPath);
}
relPath.delete(len, relPath.length());
}
private void fixAbsolutePath(InputStream original, OutputStream target, String origPrefix, String targetPrefix) throws IOException {
Properties inProps = new Properties();
inProps.load(original);
Properties outProps = new Properties();
for (String k : (Collection<String>) (Collection) inProps.keySet()) {
String orig = inProps.getProperty(k);
//XXX: should only change key or value as appropriate:
if (k.startsWith(origPrefix)) k = targetPrefix + k.substring(origPrefix.length());
if (orig.startsWith(origPrefix)) orig = targetPrefix + orig.substring(origPrefix.length());
outProps.setProperty(k, orig);
}
outProps.store(target, "");
}
}