/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2010 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]"
*
* Contributor(s):
*
* 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.
*
* 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.
*/
package org.netbeans.modules.refactoring.ruby;
import java.awt.Color;
import java.io.CharConversionException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import javax.swing.text.AttributeSet;
import javax.swing.text.StyleConstants;
import org.jrubyparser.ast.AliasNode;
import org.jrubyparser.ast.Colon2Node;
import org.jrubyparser.ast.INameNode;
import org.jrubyparser.ast.IScopingNode;
import org.jrubyparser.ast.Node;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.api.editor.settings.FontColorSettings;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenId;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.ui.OpenProjects;
import org.netbeans.editor.BaseDocument;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport;
import org.netbeans.modules.ruby.AstUtilities;
import org.netbeans.modules.ruby.RubyIndex;
import org.netbeans.modules.ruby.RubyUtils;
import org.netbeans.modules.ruby.elements.IndexedMethod;
import org.netbeans.modules.ruby.lexer.RubyTokenId;
import org.netbeans.modules.ruby.rubyproject.RubyBaseProject;
import org.netbeans.modules.ruby.rubyproject.RubyProject;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.text.CloneableEditorSupport;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.xml.XMLUtil;
/**
* Various utilities related to Ruby refactoring; the generic ones are based
* on the ones from the Java refactoring module.
*
* @author Jan Becicka
* @author Tor Norbye
*/
public class RetoucheUtils {
private RetoucheUtils() {
}
public static BaseDocument getDocument(ParserResult parserResult, FileObject fo) {
BaseDocument doc = null;
if (parserResult != null) {
doc = RubyUtils.getDocument(parserResult);
}
if (doc == null) {
try {
// Gotta open it first
DataObject od = DataObject.find(fo);
EditorCookie ec = od.getCookie(EditorCookie.class);
if (ec != null) {
doc = (BaseDocument)ec.openDocument();
}
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
return doc;
}
public static BaseDocument getDocument(ParserResult info) {
BaseDocument doc = null;
if (info != null) {
doc = RubyUtils.getDocument(info, true);
}
return doc;
}
/** Compute the names (full and simple, e.g. Foo::Bar and Bar) for the given node, if any, and return as
* a String[2] = {name,simpleName} */
public static String[] getNodeNames(Node node) {
String name = null;
String simpleName = null;
if (node instanceof Colon2Node) {
Colon2Node c2n = (Colon2Node)node;
simpleName = c2n.getName();
name = AstUtilities.getFqn(c2n);
} else if (node instanceof AliasNode) {
name = AstUtilities.getNameOrValue(((AliasNode)node).getNewName());
}
if (name == null && node instanceof INameNode) {
name = ((INameNode)node).getName();
}
if (name == null && node instanceof IScopingNode) {
if (((IScopingNode)node).getCPath() instanceof Colon2Node) {
Colon2Node c2n = (Colon2Node)((IScopingNode)node).getCPath();
simpleName = c2n.getName();
name = AstUtilities.getFqn(c2n);
} else {
name = AstUtilities.getClassOrModuleName((IScopingNode)node);
}
}
if (simpleName == null) {
simpleName = name;
}
return new String[] { name, simpleName };
}
public static CloneableEditorSupport findCloneableEditorSupport(ParserResult info) {
DataObject dob = null;
try {
dob = DataObject.find(RubyUtils.getFileObject(info));
} catch (DataObjectNotFoundException ex) {
Exceptions.printStackTrace(ex);
}
return RetoucheUtils.findCloneableEditorSupport(dob);
}
public static CloneableEditorSupport findCloneableEditorSupport(DataObject dob) {
Object obj = dob.getCookie(org.openide.cookies.OpenCookie.class);
if (obj instanceof CloneableEditorSupport) {
return (CloneableEditorSupport)obj;
}
obj = dob.getCookie(org.openide.cookies.EditorCookie.class);
if (obj instanceof CloneableEditorSupport) {
return (CloneableEditorSupport)obj;
}
return null;
}
public static String htmlize(String input) {
try {
return XMLUtil.toElementContent(input);
} catch (CharConversionException cce) {
Exceptions.printStackTrace(cce);
return input;
}
}
/** Return the most distant method in the hierarchy that is overriding the given method, or null */
public static IndexedMethod getOverridingMethod(RubyElementCtx element, ParserResult info) {
return getOverridingMethod(element, RubyIndex.get(info));
}
/** Return the most distant method in the hierarchy that is overriding the given method, or null */
public static IndexedMethod getOverridingMethod(RubyElementCtx element, FileObject fo) {
return getOverridingMethod(element, RubyIndex.get(fo));
}
private static IndexedMethod getOverridingMethod(RubyElementCtx element, RubyIndex index) {
if (index == null) {
return null;
}
String fqn = AstUtilities.getFqnName(element.getPath());
return index.getSuperMethod(fqn, element.getName(), false);
}
public static String getHtml(String text) {
StringBuffer buf = new StringBuffer();
// TODO - check whether we need ruby highlighting or rhtml highlighting
TokenHierarchy tokenH = TokenHierarchy.create(text, RubyTokenId.language());
Lookup lookup = MimeLookup.getLookup(MimePath.get(RubyUtils.RUBY_MIME_TYPE));
FontColorSettings settings = lookup.lookup(FontColorSettings.class);
@SuppressWarnings("unchecked")
TokenSequence<? extends TokenId> tok = tokenH.tokenSequence();
while (tok.moveNext()) {
Token<? extends TokenId> token = tok.token();
String category = token.id().name();
AttributeSet set = settings.getTokenFontColors(category);
if (set == null) {
category = token.id().primaryCategory();
if (category == null) {
category = "whitespace"; //NOI18N
}
set = settings.getTokenFontColors(category);
}
String tokenText = htmlize(token.text().toString());
buf.append(color(tokenText, set));
}
return buf.toString();
}
private static String color(String string, AttributeSet set) {
if (set==null) {
return string;
}
if (string.trim().length() == 0) {
return string.replace(" ", " ").replace("\n", "<br>"); //NOI18N
}
StringBuffer buf = new StringBuffer(string);
if (StyleConstants.isBold(set)) {
buf.insert(0,"<b>"); //NOI18N
buf.append("</b>"); //NOI18N
}
if (StyleConstants.isItalic(set)) {
buf.insert(0,"<i>"); //NOI18N
buf.append("</i>"); //NOI18N
}
if (StyleConstants.isStrikeThrough(set)) {
buf.insert(0,"<s>"); // NOI18N
buf.append("</s>"); // NOI18N
}
buf.insert(0,"<font color=" + getHTMLColor(StyleConstants.getForeground(set)) + ">"); //NOI18N
buf.append("</font>"); //NOI18N
return buf.toString();
}
private static String getHTMLColor(Color c) {
String colorR = "0" + Integer.toHexString(c.getRed()); //NOI18N
colorR = colorR.substring(colorR.length() - 2);
String colorG = "0" + Integer.toHexString(c.getGreen()); //NOI18N
colorG = colorG.substring(colorG.length() - 2);
String colorB = "0" + Integer.toHexString(c.getBlue()); //NOI18N
colorB = colorB.substring(colorB.length() - 2);
String html_color = "#" + colorR + colorG + colorB; //NOI18N
return html_color;
}
public static boolean isFileInOpenProject(FileObject file) {
assert file != null;
Project p = FileOwnerQuery.getOwner(file);
if (OpenProjects.getDefault().isProjectOpen(p)) {
return true;
}
return false;
}
public static boolean isOnSourceClasspath(FileObject fo) {
Project p = FileOwnerQuery.getOwner(fo);
if (p==null) {
return false;
}
Project[] opened = OpenProjects.getDefault().getOpenProjects();
for (int i = 0; i<opened.length; i++) {
if (p.equals(opened[i]) || opened[i].equals(p)) {
SourceGroup[] gr = ProjectUtils.getSources(p).getSourceGroups(RubyProject.SOURCES_TYPE_RUBY);
for (int j = 0; j < gr.length; j++) {
if (fo==gr[j].getRootFolder()) {
return true;
}
if (FileUtil.isParentOf(gr[j].getRootFolder(), fo)) {
return true;
}
}
return false;
}
}
return false;
}
// XXX Parsing API
// public static boolean isClasspathRoot(FileObject fo) {
// ClassPath cp = ClassPath.getClassPath(fo, ClassPath.SOURCE);
// if (cp != null) {
// FileObject f = cp.findOwnerRoot(fo);
// if (f != null) {
// return fo.equals(f);
// }
// }
//
// return false;
// }
public static boolean isRefactorable(FileObject file) {
return RubyUtils.canContainRuby(file) && isFileInOpenProject(file) && isOnSourceClasspath(file);
}
public static String getPackageName(FileObject folder) {
assert folder.isFolder() : "argument must be folder";
return ClassPath.getClassPath(
folder, ClassPath.SOURCE)
.getResourceName(folder, '.', false);
}
// public static FileObject getClassPathRoot(URL url) throws IOException {
// FileObject result = URLMapper.findFileObject(url);
// File f = FileUtil.normalizeFile(new File(url.getPath()));
// while (result==null) {
// result = FileUtil.toFileObject(f);
// f = f.getParentFile();
// }
// return ClassPath.getClassPath(result, ClassPath.SOURCE).findOwnerRoot(result);
// }
//
// public static ElementKind getElementKind(RubyElementCtx tph) {
// return tph.getKind();
// }
//
// public static ClasspathInfo getClasspathInfoFor(FileObject ... files) {
// assert files.length >0;
// Set<URL> dependentRoots = new HashSet<URL>();
// for (FileObject fo: files) {
// Project p = null;
// if (fo!=null) {
// p = FileOwnerQuery.getOwner(fo);
// }
// if (p!=null) {
// ClassPath classPath = ClassPath.getClassPath(fo, ClassPath.SOURCE);
// if (classPath == null) {
// return null;
// }
// FileObject ownerRoot = classPath.findOwnerRoot(fo);
// if (ownerRoot != null) {
// URL sourceRoot = URLMapper.findURL(ownerRoot, URLMapper.INTERNAL);
// dependentRoots.addAll(SourceUtils.getDependentRoots(sourceRoot));
// for (SourceGroup root:ProjectUtils.getSources(p).getSourceGroups(RubyProject.SOURCES_TYPE_RUBY)) {
// dependentRoots.add(URLMapper.findURL(root.getRootFolder(), URLMapper.INTERNAL));
// }
// } else {
// dependentRoots.add(URLMapper.findURL(fo.getParent(), URLMapper.INTERNAL));
// }
// } else {
// for(ClassPath cp: GlobalPathRegistry.getDefault().getPaths(ClassPath.SOURCE)) {
// for (FileObject root:cp.getRoots()) {
// dependentRoots.add(URLMapper.findURL(root, URLMapper.INTERNAL));
// }
// }
// }
// }
//
// ClassPath rcp = ClassPathSupport.createClassPath(dependentRoots.toArray(new URL[dependentRoots.size()]));
// ClassPath nullPath = ClassPathSupport.createClassPath(new FileObject[0]);
// ClassPath boot = files[0]!=null?ClassPath.getClassPath(files[0], ClassPath.BOOT):nullPath;
// ClassPath compile = files[0]!=null?ClassPath.getClassPath(files[0], ClassPath.COMPILE):nullPath;
//
// if (boot == null || compile == null) { // 146499
// return null;
// }
//
// ClasspathInfo cpInfo = ClasspathInfo.create(boot, compile, rcp);
// return cpInfo;
// }
//
// public static ClasspathInfo getClasspathInfoFor(RubyElementCtx ctx) {
// return getClasspathInfoFor(ctx.getFileObject());
// }
//
public static Collection<FileObject> getProjectRoots(FileObject fileInProject) {
Project owner = FileOwnerQuery.getOwner(fileInProject);
// XXX won't owner always be a RubyBaseProject?
if (owner instanceof RubyBaseProject) {
Collection<FileObject> result = new HashSet<FileObject>();
result.addAll(Arrays.asList(((RubyBaseProject) owner).getSourceRootFiles()));
result.addAll(Arrays.asList(((RubyBaseProject) owner).getTestSourceRootFiles()));
return result;
} else {
// fallback
return QuerySupport.findRoots(fileInProject,
null, Collections.<String>emptySet(), Collections.<String>emptySet());
}
}
public static Set<FileObject> getRubyFilesInProject(FileObject fileInProject) {
Set<FileObject> files = new HashSet<FileObject>(100);
Project owner = FileOwnerQuery.getOwner(fileInProject);
Collection<FileObject> sourceRoots = getProjectRoots(fileInProject);
for (FileObject root : sourceRoots) {
String name = root.getName();
// Skip non-refactorable parts in renaming
if (name.equals("vendor") || name.equals("script")) { // NOI18N
continue;
}
addRubyFiles(files, root);
}
return files;
}
private static void addRubyFiles(Set<FileObject> files, FileObject f) {
if (f.isFolder()) {
for (FileObject child : f.getChildren()) {
addRubyFiles(files, child);
}
} else if (RubyUtils.canContainRuby(f)) {
files.add(f);
}
}
}