/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2013 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 2013 Sun Microsystems, Inc.
*/
package org.nbphpcouncil.modules.php.yii.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.nbphpcouncil.modules.php.yii.Yii;
import org.nbphpcouncil.modules.php.yii.YiiModule;
import org.nbphpcouncil.modules.php.yii.YiiModuleFactory;
import org.nbphpcouncil.modules.php.yii.YiiPhpFrameworkProvider;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.modules.parsing.api.ParserManager;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.netbeans.modules.parsing.api.Source;
import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.modules.php.api.editor.PhpClass;
import org.netbeans.modules.php.api.phpmodule.PhpModule;
import org.netbeans.modules.php.api.util.FileUtils;
import org.netbeans.modules.php.api.util.StringUtils;
import org.netbeans.modules.php.editor.parser.api.Utils;
import org.netbeans.modules.php.editor.parser.astnodes.ArrayElement;
import org.netbeans.modules.php.editor.parser.astnodes.Expression;
import org.netbeans.modules.php.editor.parser.astnodes.visitors.DefaultVisitor;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Exceptions;
/**
*
* @author junichi11
*/
public class YiiUtils {
private static final String YII_INCLUDE_PATH_REGEX = "^\\$yii *=.+'(.+/framework)/yii\\.php';$"; // NOI18N
private static final String CONTROLLER_SUFFIX = "Controller"; // NOI18N
private static final String ACTION_METHOD_PREFIX = "action"; // NOI18N
private static final String VIEW_RELATIVE_PATH_FORMAT = "../..%s%s/views/%s%s/%s.php"; // NOI18N
private static final String CONTROLLER_RELATIVE_PATH_FORMAT = "../../..%s%s/controllers/%s%s.php"; // NOI18N
private static final String THEME_PATH = "/../themes/%s"; // NOI18N
private static final String NBPROJECT = "nbproject"; // NOI18N
private static final String MODELS_PATH = "models"; // NOI18N
private static final String TESTS_PATH = "tests"; // NOI18N
private static final Logger LOGGER = Logger.getLogger(YiiUtils.class.getName());
/**
* Check whether php module is yii
*
* @param phpModule
* @return true if php module is yii, otherwiser false
*/
public static boolean isYii(PhpModule phpModule) {
if (phpModule == null) {
return false;
}
return YiiPhpFrameworkProvider.getInstance().isInPhpModule(phpModule);
}
private static PhpModule getPhpModule(FileObject fileObject) {
return PhpModule.Factory.forFileObject(fileObject);
}
/**
* Get include path.
*
* @param index index.php file
* @return if exists include path, string. otherwise null.
*/
public static List<String> getIncludePath(FileObject index) {
List<String> lines;
List<String> includePath = new ArrayList<String>();
try {
lines = index.asLines("UTF-8"); // NOI18N
Pattern pattern = Pattern.compile(YII_INCLUDE_PATH_REGEX);
for (String line : lines) {
Matcher matcher = pattern.matcher(line);
if (matcher.find()) {
String path = matcher.group(1);
if (path.startsWith("/")) { // NOI18N
path = path.replaceFirst("/", ""); // NOI18N
}
includePath.add(path);
break;
}
}
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
return includePath;
}
/**
* Check view file
*
* @param fo
* @return true if file is view file, otherwise false.
*/
public static boolean isView(FileObject fo) {
if (fo == null
|| !fo.isData()
|| !FileUtils.isPhpFile(fo)) {
return false;
}
String subpath = getPathFromWebroot(fo);
if (StringUtils.isEmpty(subpath)) {
return false;
}
return subpath.contains("/views/");
}
/**
* Get path from webroot directory.
*
* @param fo
* @return
*/
private static String getPathFromWebroot(FileObject fo) {
PhpModule phpModule = getPhpModule(fo);
// return null if use external files #11
if (phpModule == null) {
return null;
}
String path = fo.getPath();
YiiModule yiiModule = YiiModuleFactory.create(phpModule);
FileObject webroot = yiiModule.getWebroot();
String webrootPath = webroot.getPath();
return path.replace(webrootPath, ""); // NOI18N
}
/**
* Create view file for target path.
*
* @param baseDirectory
* @param targetPath
* @return file if file was created, otherwise null.
*/
public static FileObject createFile(FileObject baseDirectory, String targetPath) {
File file = new File(FileUtil.toFile(baseDirectory), targetPath);
createParents(file);
try {
if (file.createNewFile()) {
return baseDirectory.getFileObject(targetPath);
}
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "Fail: can't create file {0}", targetPath);
}
return null;
}
/**
* Create parent directories if it doesn't exist.
*
* @param file
* @return true if create parents, otherwise false.
*/
private static boolean createParents(File file) {
File parentFile = file.getParentFile();
return parentFile.mkdirs();
}
/**
* Get relative path to view file from controller.
*
* @param controller
* @param actionId action name(view file name). e.g actionIndex -> index
* @param themeName theme name
* @return view file object
*/
public static String getRelativePathToView(FileObject controller, String actionId, String themeName) {
String controllerId = getViewFolderName(controller.getName());
String subPath = getSubDirectoryPathForController(controller);
if (subPath == null) {
return null;
}
return getRelativePathToView(controller, subPath, controllerId, actionId, themeName);
}
/**
* Get sub directory path for controller file. If file doesn't exist within
* protected/controllers, return null. Otherwise return empty string "" or
* path. For example : protected/controllers/subdir/SiteController.php ->
* subdir/
*
* @param controller
* @return sub directory path from controllers dir to file.
*/
public static String getSubDirectoryPathForController(FileObject controller) {
String filePath = controller.getPath();
if (!filePath.contains("/controllers/")) { // NOI18N
return null;
}
String subPath = filePath.replaceAll(".+/controllers/", ""); // NOI18N
return subPath.replace(controller.getNameExt(), ""); // NOI18N
}
/**
* Get relative path to view file from controller.
*
* @param controller
* @param controllerId
* @param actionId
* @param themeName
* @return
*/
public static String getRelativePathToView(FileObject controller, String subPath, String controllerId, String actionId, String themeName) {
if (controller == null) {
return null;
}
String themePath = ""; // NOI18N
if (!StringUtils.isEmpty(themeName)) {
themePath = String.format(THEME_PATH, themeName);
}
// add depth for sub path
String subpathDepth = toSubpathDepth(subPath);
// create relative path from controller to view file
return String.format(VIEW_RELATIVE_PATH_FORMAT, subpathDepth, themePath, subPath, controllerId, actionId);
}
/**
* Get theme name from main.php file.
*
* @param main
* @return theme name if find the theme, otherwise empty string.
*/
public static String getThemeName(FileObject main) {
final Set<String> themes = new HashSet<String>();
try {
ParserManager.parse(Collections.singleton(Source.create(main)), new UserTask() {
@Override
public void run(ResultIterator resultIterator) throws Exception {
if (resultIterator == null) {
return;
}
ParserResult parserResult = (ParserResult) resultIterator.getParserResult();
final MainVisitor mainVisitor = new MainVisitor();
mainVisitor.scan(Utils.getRoot(parserResult));
themes.addAll(mainVisitor.getThemeName());
}
});
} catch (ParseException ex) {
Exceptions.printStackTrace(ex);
}
String themeName = ""; // NOI18N
for (String theme : themes) {
themeName = theme;
break;
}
return themeName;
}
/**
* For auto create option.
*
* @param controller
* @param pathToView
* @return
*/
public static FileObject createViewFileAuto(FileObject controller, String pathToView) {
String viewPath = FileUtil.normalizePath(controller.getParent().getPath() + pathToView);
File file = new File(viewPath);
FileObject view = null;
try {
// create sub directories
File parentFile = file.getParentFile();
if (!parentFile.exists()) {
parentFile.mkdirs();
}
// create file
if (file.createNewFile()) {
view = FileUtil.toFileObject(file);
}
} catch (IOException ex) {
LOGGER.log(Level.WARNING, "Can't create view file : {0}", viewPath);
}
return view;
}
/**
* Check controller file
*
* @param fo
* @return true if file is controller file, otherwise false.
*/
public static boolean isController(FileObject fo) {
String subPath = getSubDirectoryPathForController(fo);
if (subPath == null) {
return false;
}
return fo.isData() && FileUtils.isPhpFile(fo) && fo.getName().endsWith(CONTROLLER_SUFFIX);
}
/**
* Get controller file object
*
* @param view
* @return controller file object
*/
public static FileObject getController(FileObject view) {
if (!isView(view)) {
return null;
}
// views
String subpath = getSubpathToView(view);
String controllerId = ""; // NOI18N
String nestedPath = ""; // NOI18N
if (!subpath.isEmpty()) {
int lastSlash = subpath.lastIndexOf("/"); // NOI18N
if (lastSlash == -1) {
controllerId = subpath;
nestedPath = ""; // NOI18N
} else {
controllerId = subpath.substring(lastSlash + 1);
nestedPath = subpath.substring(0, lastSlash + 1);
}
}
// add depth for sub path
FileObject controller = null;
String controllerName = getControllerFileName(controllerId);
if (isInModules(view)) {
String nestedPathDepth = toSubpathDepth(nestedPath);
String format = String.format(CONTROLLER_RELATIVE_PATH_FORMAT, nestedPathDepth, "", nestedPath, controllerName);
controller = view.getFileObject(format);
} else {
PhpModule phpModule = PhpModule.Factory.forFileObject(view);
YiiModule yiiModule = YiiModuleFactory.create(phpModule);
if (yiiModule != null) {
FileObject controllersDirectory = yiiModule.getControllers();
if (controllersDirectory == null) {
return null;
}
// nested directory
String path = String.format("%s%s.php", nestedPath, controllerName); // NOI18N
controller = controllersDirectory.getFileObject(path);
// has sub directory
if (controller == null) {
if (!subpath.isEmpty()) {
int firstSlash = subpath.indexOf("/"); // NOI18N
if (firstSlash == -1) {
controllerId = subpath;
} else {
controllerId = subpath.substring(0, firstSlash);
}
}
controllerName = getControllerFileName(controllerId);
path = String.format("%s.php", controllerName); // NOI18N
controller = controllersDirectory.getFileObject(path);
}
}
}
return controller;
}
private static String getSubpathToView(FileObject view) {
String subpathFromWebroot = getPathFromWebroot(view);
String subpath = ""; // NOI18N
if (subpathFromWebroot.contains("/views/")) { // NOI18N
subpath = subpathFromWebroot.replaceAll(".+/views/", ""); // NOI18N
subpath = subpath.replace("/" + view.getNameExt(), ""); // NOI18N
}
return subpath;
}
/**
* Get subpath depth.
*
* @param path
* @return
*/
private static String toSubpathDepth(String path) {
StringBuilder sb = new StringBuilder();
int startIndex = 0;
while (startIndex != -1) {
startIndex++;
startIndex = path.indexOf("/", startIndex); // NOI18N
if (startIndex > 0) {
sb.append("/.."); // NOI18N
}
}
return sb.toString();
}
/**
* Get controller file name. e.g. SiteController,DemoController
*
* @param viewFolderName each views directory name
* @return controller file name. ControllerName + Controller
*/
public static String getControllerFileName(String viewFolderName) {
viewFolderName = toFirstUpperCase(viewFolderName);
if (viewFolderName == null) {
return null;
}
return viewFolderName + CONTROLLER_SUFFIX;
}
/**
* Check controller class name
*
* @param controller
* @return true if controller class, otherwise false
*/
public static boolean isControllerName(String controller) {
if (controller == null) {
return false;
}
return controller.endsWith(CONTROLLER_SUFFIX);
}
/**
* Get controller action method name
*
* @param view file name
* @return action method name
*/
public static String getActionMethodName(String view) {
if (view == null || view.isEmpty()) {
return null;
}
return ACTION_METHOD_PREFIX + toFirstUpperCase(view);
}
/**
* Get view folder name
*
* @param controllerName
* @return
*/
public static String getViewFolderName(String controllerName) {
if (!isControllerName(controllerName)) {
return null;
}
String name = controllerName.replace(CONTROLLER_SUFFIX, ""); // NOI18N
// #44 e.g. AbcDefController -> abcDef
return toFirstLowerCase(name);
}
/**
* Get view name
*
* @param method
* @return
*/
public static String getViewName(PhpClass.Method method) {
String name = method.getName();
if (!isActionMethodName(name)) {
return null;
}
name = name.replace(ACTION_METHOD_PREFIX, ""); // NOI18N
return name.toLowerCase();
}
/**
* Check action method name
*
* @param name
* @return
*/
public static boolean isActionMethodName(String name) {
if (name == null || !name.startsWith(ACTION_METHOD_PREFIX) || name.isEmpty()) {
return false;
}
name = name.replace(ACTION_METHOD_PREFIX, ""); // NOI18N
String first = name.substring(0, 1);
return first.equals(first.toUpperCase());
}
/**
* Converts the first of characters to upper case.
*
* Examples: "apple" -> "Apple", "minBanana" -> "MiniBanana", "ORANGE" ->
* "ORANGE"
*
* @param string
* @return the converted string
*/
public static String toFirstUpperCase(String string) {
if (string == null || string.isEmpty()) {
return null;
}
return string.substring(0, 1).toUpperCase() + string.substring(1);
}
/**
* Converts the first of characters to lower case.
*
* Examples: "Apple" -> "apple", "MiniBanana" -> "miniBanana", "ORANGE" ->
* "oRANGE"
*
* @param string
* @return the converted string
*/
public static String toFirstLowerCase(String string) {
if (string == null || string.isEmpty()) {
return string;
}
if (string.length() == 1) {
return string.toLowerCase();
}
return string.substring(0, 1).toLowerCase() + string.substring(1);
}
/**
* Create code completion file
*
* @param phpModule
* @throws IOException
*/
public static void createCodeCompletionFile(PhpModule phpModule) throws IOException {
if (!isYii(phpModule)) {
return;
}
FileObject codeCompletion = FileUtil.getConfigFile(Yii.YII_CODE_COMPLETION_CONFIG_PATH);
FileObject nbproject = getNbproject(phpModule);
if (nbproject != null && codeCompletion != null) {
codeCompletion.copy(nbproject, codeCompletion.getName(), codeCompletion.getExt());
}
}
/**
* Get nbproject directory
*
* @param phpModule
* @return
*/
public static FileObject getNbproject(PhpModule phpModule) {
FileObject projectDirectory = phpModule.getProjectDirectory();
FileObject nbproject = null;
return projectDirectory.getFileObject(NBPROJECT);
}
/**
* Get views directory (protected/views)
*
* @param phpModule
* @return
*/
public static FileObject getViewsDirectory(PhpModule phpModule) {
YiiModule yiiModule = YiiModuleFactory.create(phpModule);
if (yiiModule != null) {
return yiiModule.getViews();
}
return null;
}
/**
* Get controllers directory (protected/controllers)
*
* @param phpModule
* @return
*/
public static FileObject getControllersDirectory(PhpModule phpModule) {
YiiModule yiiModule = YiiModuleFactory.create(phpModule);
if (yiiModule != null) {
return yiiModule.getControllers();
}
return null;
}
/**
* Get models directory (protected/models)
*
* @param phpModule
* @return
*/
public static FileObject getModelsDirectory(PhpModule phpModule) {
return getDirectory(phpModule, MODELS_PATH);
}
/**
* Get tests directory (protected/tests)
*
* @param phpModule
* @return
*/
public static FileObject getTestsDirectory(PhpModule phpModule) {
YiiModule yiiModule = YiiModuleFactory.create(phpModule);
if (yiiModule != null) {
FileObject application = yiiModule.getApplication();
if (application != null) {
return application.getFileObject(TESTS_PATH);
}
}
return null;
}
/**
* Get themes directory (protected/themes).
*
* @param phpModule
* @return
*/
public static FileObject getThemesDirectory(PhpModule phpModule) {
YiiModule yiiModule = YiiModuleFactory.create(phpModule);
if (yiiModule != null) {
return yiiModule.getThemes();
}
return null;
}
/**
* Get directory
*
* @param phpModule
* @param path relative path from source directory
* @return
*/
public static FileObject getDirectory(PhpModule phpModule, String path) {
YiiModule yiiModule = YiiModuleFactory.create(phpModule);
FileObject sourceDirectory = yiiModule.getWebroot();
if (sourceDirectory == null) {
return null;
}
return sourceDirectory.getFileObject(path);
}
private static class MainVisitor extends DefaultVisitor {
private static final String THEME = "theme"; // NOI18N
private final Set<String> themeName = new HashSet<String>();
@Override
public void visit(ArrayElement node) {
super.visit(node);
Expression key = node.getKey();
String keyName = YiiCodeUtils.getStringValue(key);
if (keyName.equals(THEME)) {
String value = YiiCodeUtils.getStringValue(node.getValue());
if (!value.isEmpty()) {
themeName.add(value);
}
}
}
public synchronized Set<String> getThemeName() {
return themeName;
}
}
/**
* Check whether file is within modules directory.
*
* @param fileObject current file
* @return true if file exists within modules directory, otherwise false.
*/
public static boolean isInModules(FileObject fileObject) {
String pathFromWebroot = getPathFromWebroot(fileObject);
if (pathFromWebroot != null) {
return pathFromWebroot.contains("/modules/"); // NOI18N
}
return false;
}
/**
* Get module name.
*
* @param fileObject
* @return module name in current file. if file doesn't exist in modules,
* null.
*/
public static String getModuleName(FileObject fileObject) {
if (fileObject == null || !isInModules(fileObject)) {
return null;
}
String path = getPathFromWebroot(fileObject);
if (path == null) {
return null;
}
path = path.replaceAll(".+/modules/", path); // NOI18N
return path.substring(0, path.indexOf("/")); // NOI18N
}
/**
* Get current module directory.
*
* @param fileObject current file
* @return current module directory.
*/
public static FileObject getCurrentModuleDirectory(FileObject fileObject) {
if (fileObject == null || !isInModules(fileObject)) {
return null;
}
String path = getPathFromWebroot(fileObject);
if (path == null) {
return null;
}
String modules = "/modules/"; // NOI18N
int modulesIndex = path.lastIndexOf(modules);
// contains module name
int moduleIndex = path.indexOf("/", modulesIndex + modules.length()); // NOI18N
String modulePath = path.substring(0, moduleIndex);
YiiModule yiiModule = YiiModuleFactory.create(PhpModule.Factory.forFileObject(fileObject));
FileObject webroot = yiiModule.getWebroot();
if (webroot == null) {
return null;
}
return webroot.getFileObject(modulePath);
}
/**
* Sort files.
*
* @param files
* @param desc true if order by desc, false if asc.
*/
public static void sort(FileObject[] files, final boolean desc) {
Arrays.sort(files, new ComparatorImpl(desc));
}
/**
* Sort files order by asc.
*
* @param files
*/
public static void sort(FileObject[] files) {
sort(files, false);
}
/**
* Sort files.
*
* @param files
* @param desc
*/
public static void sort(List<FileObject> files, final boolean desc) {
Collections.sort(files, new ComparatorImpl(desc));
}
/**
* Sort files order by asc.
*
* @param files
*/
public static void sort(List<FileObject> files) {
sort(files, false);
}
private static class ComparatorImpl implements Comparator<FileObject> {
private final boolean desc;
public ComparatorImpl(boolean desc) {
this.desc = desc;
}
@Override
public int compare(FileObject o1, FileObject o2) {
if (desc) {
return o2.getName().compareToIgnoreCase(o1.getName());
}
return o1.getName().compareToIgnoreCase(o2.getName());
}
}
}