/*
* The contents of this file are subject to the terms of the Common Development
* and Distribution License (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.html
* or http://www.netbeans.org/cddl.txt.
*
* When distributing Covered Code, include this CDDL Header Notice in each file
* and include the License file at http://www.netbeans.org/cddl.txt.
* If applicable, add the following below the CDDL Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.gwt4nb;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import java.util.regex.Pattern;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectManager;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.modules.gwt4nb.settings.GWTSettings;
import org.netbeans.spi.project.support.ant.EditableProperties;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileSystem.AtomicAction;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.URLMapper;
import org.openide.util.Exceptions;
import org.openide.util.Mutex;
import org.openide.util.MutexException;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* Utilities
*
* @author tomslot
* @author see https://github.com/gwt4nb/gwt4nb/
*/
public class GWT4NBUtil {
/**
* GWT installation dir property name for
* ProjectUtils.getPreferences(project, getClass(), false).get(...)
* build-gwt.xml uses the same value.
*/
public static final String GWT_DIR_PROPERTY = "GWTDir"; // NOI18N
/**
* Logger for the whole module.
*/
public static final Logger LOGGER = Logger.getLogger(GWT4NBUtil.class.getName());
static {
System.setProperty(LOGGER.getName() + ".level", "100"); // NOI18N
try {
LogManager.getLogManager().readConfiguration();
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
} catch (SecurityException ex) {
Exceptions.printStackTrace(ex);
}
}
public static void replaceInFile(FileObject file, String searchedString,
String replacement) {
replaceInFile(file, new String[]{searchedString},
new String[]{replacement});
}
public static void replaceInFile(FileObject file,
String searchedStrings[], String replacements[]) {
//TODO: provide a better implementation
if (searchedStrings.length != replacements.length) {
throw new IllegalArgumentException();
}
String orgFileContent = null;
InputStream is = null;
try {
is = file.getInputStream();
byte rawContent[] = new byte[is.available()];
is.read(rawContent);
orgFileContent = new String(rawContent);
is.close();
String alteredContent = orgFileContent;
for (int i = 0; i < searchedStrings.length; i++) {
alteredContent = alteredContent.replace(
searchedStrings[i], replacements[i]);
}
FileLock lock = file.lock();
PrintWriter writer = new PrintWriter(file.getOutputStream(lock));
writer.print(alteredContent);
writer.close();
lock.releaseLock();
} catch (IOException ex) {
GWT4NBUtil.LOGGER.log(Level.SEVERE, "", ex); // NOI18N
} finally {
try {
if (is != null) {
is.close();
}
} catch (IOException ex) {
GWT4NBUtil.LOGGER.log(Level.SEVERE, "", ex); // NOI18N
}
}
}
/**
* Copies a resource to a file and replaces variables.
*
* @param res {@link Class#getResourceAsStream(java.lang.String)}
* @param to target file
* @param mapFrom regular expressions to search in every line
* @param mapTo replacement strings
*/
public static void copyResource(final String res, final FileObject to,
final String[] mapFrom, final String[] mapTo) throws IOException {
copyResource(res, to, mapFrom, mapTo, false);
}
/**
* Copies a resource to a file and replaces variables.
*
* @param res {@link Class#getResourceAsStream(java.lang.String)}
* @param to target file
* @param mapFrom regular expressions to search
* @param mapTo replacement strings
* @param multiLine whether to search/replace within multiple lines
*/
public static void copyResource(final String res, final FileObject to,
final String[] mapFrom, final String[] mapTo, boolean multiLine) throws IOException {
assert res != null;
assert to != null;
assert mapFrom != null;
assert mapTo != null;
assert mapFrom.length == mapTo.length;
InputStream _in = GWTFrameworkProvider.class.getClassLoader().
getResourceAsStream(res);
assert _in != null;
BufferedReader in = new BufferedReader(new InputStreamReader(_in));
try {
FileLock lock = to.lock();
try {
if (multiLine) {
//first copy input to String without regex mapping
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter(sw);
try {
copyStream(in, out, new String[0], new String[0]);
} finally {
out.close();
}
//regex/replace an entire content string
String content = sw.toString();
for (int i = 0; i < mapFrom.length; i++) {
content = content.replaceAll(mapFrom[i], mapTo[i]);
}
//write to the target
OutputStreamWriter writer = new OutputStreamWriter(to.getOutputStream(lock));
try {
writer.write(content);
} finally {
writer.close();
}
} else {
PrintWriter out = new PrintWriter(
new OutputStreamWriter(to.getOutputStream(lock)));
try {
copyStream(in, out, mapFrom, mapTo);
} finally {
out.close();
}
}
} finally {
lock.releaseLock();
}
} finally {
in.close();
}
}
public static void copyStream(final BufferedReader in,
final PrintWriter out, final String[] mapFrom,
String[] mapTo) throws IOException {
String line;
while ((line = in.readLine()) != null) {
for (int i = 0; i < mapFrom.length; i++) {
line = line.replaceAll(mapFrom[i], mapTo[i]);
}
out.println(line);
}
}
public static boolean isValidJavaIdentifier(String txt) {
if (txt.length() == 0 || !Character.isJavaIdentifierStart(txt.charAt(0))) {
return false;
}
for (int i = 1; i < txt.length(); i++) {
if (!Character.isJavaIdentifierPart(txt.charAt(i))) {
return false;
}
}
return true;
}
/**
* Changes the associated GWT directory for a project.
*
* @param project a GWT project
* @param d new GWT directory
*/
public static void setProjectGWTDir(Project project, File d) {
Preferences pp
= ProjectUtils.getPreferences(project, GWT4NBUtil.class, false);
pp.put(GWT4NBUtil.GWT_DIR_PROPERTY, d.getAbsolutePath());
try {
pp.flush();
} catch (BackingStoreException ex) {
Exceptions.printStackTrace(ex);
}
}
/**
* Returns GWT directory for a project
*
* @param project a project
* @return GWT directory or null
*/
public static File getProjectGWTDir(Project project) {
assert project != null;
Preferences pp
= ProjectUtils.getPreferences(project, GWT4NBUtil.class, false);
String gwtLocation = pp.get(GWT4NBUtil.GWT_DIR_PROPERTY, null);
// for older projects read the property from
// nbproject/gwt.properties
if (gwtLocation == null) {
gwtLocation = GWTProjectInfo.readGWTPropertyAnt(project,
"gwt.install.dir"); // NOI18N
if (gwtLocation != null) {
pp.put(GWT4NBUtil.GWT_DIR_PROPERTY, gwtLocation);
try {
pp.flush();
} catch (BackingStoreException ex) {
Exceptions.printStackTrace(ex);
}
}
}
if (gwtLocation == null) {
File f = GWTSettings.getGWTLocation();
if (f != null) {
gwtLocation = f.getAbsolutePath();
pp.put(GWT4NBUtil.GWT_DIR_PROPERTY, gwtLocation);
try {
pp.flush();
} catch (BackingStoreException ex) {
Exceptions.printStackTrace(ex);
}
}
}
if (gwtLocation != null) {
return new File(gwtLocation);
} else {
return null;
}
}
/**
* Parses an XML file
*
* @param fo XML file
* @return parsed file
*/
public static Document parseXMLFile(FileObject fo)
throws ParserConfigurationException, SAXException, IOException {
DocumentBuilderFactory dbf
= javax.xml.parsers.DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
InputStream is = fo.getInputStream();
Document doc;
try {
doc = db.parse(is);
} finally {
is.close();
}
return doc;
}
/**
* Checks for presence of pom.xml
*
* @param project a project
* @return true = Maven project
*/
public static boolean isMavenProject(Project project) {
final FileObject projectDirectory = project.getProjectDirectory();
FileObject pom = projectDirectory.getFileObject("pom.xml"); // NOI18N
return pom != null;
}
/**
* Finds a FileObject for a resource in this module.
*
* @param path /org/netbeans/modules/gwt4nb/A.txt
* @return the corresponding FileObject
*/
public static FileObject getModuleResource(String path) {
return URLMapper.findFileObject(GWT4NBUtil.class.getResource(path));
}
/**
* This method should be called if an unexpected exception occures.
* Something like BadLocationException if you know the text insert position
* is right.
*
* @param ex the "unexpected" exception
*/
public static void unexpectedException(Throwable ex) {
Exceptions.printStackTrace(ex);
}
public static EditableProperties getEditableProperties(final Project prj,
final String propertiesPath)
throws IOException {
try {
return ProjectManager.mutex().readAccess(
new Mutex.ExceptionAction<EditableProperties>() {
public EditableProperties run() throws IOException {
FileObject propertiesFo = prj.getProjectDirectory().
getFileObject(propertiesPath);
EditableProperties ep = null;
if (propertiesFo != null) {
InputStream is = null;
ep = new EditableProperties();
try {
is = propertiesFo.getInputStream();
ep.load(is);
} finally {
if (is != null) {
is.close();
}
}
}
return ep;
}
});
} catch (MutexException ex) {
return null;
}
}
public static void storeEditableProperties(final Project prj,
final String propertiesPath, final EditableProperties ep)
throws IOException {
try {
ProjectManager.mutex().writeAccess(
new Mutex.ExceptionAction<Void>() {
public Void run() throws IOException {
FileObject propertiesFo = prj.getProjectDirectory().
getFileObject(propertiesPath);
if (propertiesFo != null) {
OutputStream os = null;
try {
os = propertiesFo.getOutputStream();
ep.store(os);
} finally {
if (os != null) {
os.close();
}
}
}
return null;
}
});
} catch (MutexException ex) {
}
}
/**
* Checks a GWT module name for validity.
*
* @param name name of a GWT module
* @return true = valid
*/
public static boolean isValidGWTModuleName(String name) {
boolean validModule = true;
String parts[] = name.split("\\."); // NOI18N
if (parts.length < 2 || name.endsWith(".")) { // NOI18N
validModule = false;
} else {
for (String part : parts) {
if (!GWT4NBUtil.isValidJavaIdentifier(part)) {
validModule = false;
break;
}
}
}
return validModule;
}
/**
* Finds version of a GWT installation.
*
* @param gwtFolder something like C:\libs\gwt-windows-1.7.0
* @return version or null if it cannot be determined
*/
public static String findGWTVersion(final File gwtFolder) {
try {
final FindGWTVersionAction findGWTVersion
= new FindGWTVersionAction(gwtFolder);
FileUtil.runAtomicAction(findGWTVersion);
return findGWTVersion.getVersion();
} catch (final IOException ex) {
Exceptions.printStackTrace(ex);
}
return null;
}
/**
* Searches for "gwt-dev-\\w*.jar" in a directory.
*
* @param gwtRoot something like C:\libs\gwt-windows-1.7.0
* @return "gwt-dev-\\w*.jar" or null
*/
public static FileObject getGWTDevArchive(final File gwtRoot) {
assert gwtRoot != null;
final File[] files = gwtRoot.listFiles();
if (files != null) {
final Pattern pattern = Pattern.compile("gwt-dev(-\\w*)?.jar", // NOI18N
Pattern.CASE_INSENSITIVE);
for (final File file : files) {
if (pattern.matcher(file.getName()).matches()) {
return FileUtil.toFileObject(FileUtil.normalizeFile(file));
}
}
}
return null;
}
private static class FindGWTVersionAction implements AtomicAction {
private String version = null;
private final File gwtFolder;
public FindGWTVersionAction(final File gwtFolder) {
this.gwtFolder = gwtFolder;
}
@SuppressWarnings("rawtypes")
public void run() throws IOException {
ClassLoader cl = null;
try {
final FileObject devjar = getGWTDevArchive(gwtFolder);
if (devjar != null) {
final File devjar_ = FileUtil.toFile(devjar);
if (devjar_ != null) {
final URL url = devjar_.toURI().toURL();
cl = AccessController.doPrivileged(
new PrivilegedAction<ClassLoader>() {
public ClassLoader run() {
return new URLClassLoader(new URL[]{url});
}
});
final Class cls = cl.loadClass(
"com.google.gwt.dev.About"); // NOI18N
final Field gwtVers = cls.getField("GWT_VERSION_NUM"); // NOI18N
this.version = gwtVers.get(null).toString();
}
}
} catch (IllegalArgumentException ex) {
Exceptions.printStackTrace(ex);
} catch (IllegalAccessException ex) {
Exceptions.printStackTrace(ex);
} catch (NoSuchFieldException ex) {
// gwt2.6.0
// https://github.com/ksfreitas/gwt4nb/issues/26
// I'm not sure if this function exists in older releases
if (cl != null) {
try {
final Class cls = cl.loadClass(
"com.google.gwt.dev.About"); // NOI18N
final Method method = cls.getMethod("getGwtVersionNum", new Class[]{});
this.version = (String) method.invoke(null, new Object[]{});
} catch (Exception ex1) {
Exceptions.printStackTrace(ex1);
}
} else {
Exceptions.printStackTrace(ex);
}
} catch (SecurityException ex) {
Exceptions.printStackTrace(ex);
} catch (ClassNotFoundException ex) {
Exceptions.printStackTrace(ex);
}
}
/**
* @return GWT version or null if it cannot be determined
*/
private String getVersion() {
return version;
}
}
/**
* Joins multiple strings together.
*
* @param values strings
* @param sep separator
* @return created string
*/
public static String join(Collection<String> values, String sep) {
StringBuilder sb = new StringBuilder();
for (String s : values) {
if (sb.length() != 0) {
sb.append(sep);
}
sb.append(s);
}
return sb.toString();
}
}