/** * Copyright (c) 2001-2017 Mathew A. Nelson and Robocode contributors * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://robocode.sourceforge.net/license/epl-v10.html */ package net.sf.robocode.repository.items; import net.sf.robocode.io.FileUtil; import net.sf.robocode.io.Logger; import net.sf.robocode.io.RobocodeProperties; import net.sf.robocode.io.URLJarCollector; import static net.sf.robocode.io.Logger.logError; import net.sf.robocode.repository.IRobotItem; import net.sf.robocode.repository.RobotProperties; import net.sf.robocode.repository.RobotType; import net.sf.robocode.repository.root.ClasspathRoot; import net.sf.robocode.repository.root.IRepositoryRoot; import net.sf.robocode.security.HiddenAccess; import net.sf.robocode.core.Container; import net.sf.robocode.host.IHostManager; import net.sf.robocode.version.IVersionManager; import robocode.control.RobotSpecification; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; import java.util.HashSet; import java.util.Set; /** * @author Pavel Savara (original) * @author Flemming N. Larsen (contributor) */ public class RobotItem extends RobotSpecItem implements IRobotItem { private static final long serialVersionUID = 1L; // Allowed maximum length for a robot's full package name private static final int MAX_FULL_PACKAGE_NAME_LENGTH = 32; // Allowed maximum length for a robot's short class name private static final int MAX_SHORT_CLASS_NAME_LENGTH = 32; protected static final String ROBOT_PLATFORM = "robot.platform"; private static final String ROBOT_CLASSNAME = "robot.classname"; private static final String ROBOT_VERSION = "robot.version"; private static final String ROBOT_DESCRIPTION = "robot.description"; private static final String ROBOT_AUTHOR_NAME = "robot.author.name"; private static final String ROBOT_WEBPAGE = "robot.webpage"; private static final String ROBOCODE_VERSION = "robocode.version"; private static final String ROBOT_INCLUDE_SOURCE = "robot.include.source"; private static final String ROBOT_INCLUDE_DATA = "robot.include.data"; private static final String ROBOT_CODESIZE = "robot.codesize"; // File extensions private static final String CLASS_EXTENSION = ".class"; private static final String PROPERTIES_EXTENSION = ".properties"; private static final String HTML_EXTENSION = ".html"; private static final boolean ALWAYS_USE_CACHE_FOR_DATA = System.getProperty("ALWAYSUSECACHEFORDATA", "false").equals( "true"); private RobotType robotType; private URL classPathURL; private Set<URL> sourcePathURLs; // This is a Set in order to avoid duplicates private URL classURL; private URL propertiesURL; private String className; protected boolean isPropertiesLoaded; public RobotItem(URL itemURL, IRepositoryRoot root) { super(itemURL, root); isValid = true; classPathURL = root.getURL(); sourcePathURLs = new HashSet<URL>(); } private void populate() { populatePropertiesURLFromClassURL(); populateClassURLFromPropertiesURL(); loadProperties(); } public void setClassURL(URL classUrl) { this.classURL = classUrl; populate(); } public void setPropertiesURL(URL propertiesUrl) { this.propertiesURL = propertiesUrl; populate(); } public void setClassPathURL(URL classPathUrl) { this.classPathURL = classPathUrl; } public void addSourcePathURL(URL sourcePathUrl) { sourcePathURLs.add(sourcePathUrl); } // ------------------------------------- // URL initialization // ------------------------------------- private void populatePropertiesURLFromClassURL() { if (propertiesURL == null && classURL != null) { final String path = classURL.toString().replaceFirst("\\" + CLASS_EXTENSION, PROPERTIES_EXTENSION); try { propertiesURL = new URL(path); } catch (MalformedURLException e) { Logger.logError(e); } if (isInvalidURL(propertiesURL)) { propertiesURL = null; } } } private void populateClassURLFromPropertiesURL() { if (classURL == null && propertiesURL != null) { final String path = propertiesURL.toString().replaceAll("\\" + PROPERTIES_EXTENSION, CLASS_EXTENSION); try { classURL = new URL(path); } catch (MalformedURLException e) { Logger.logError(e); } if (isInvalidURL(classURL)) { classURL = null; } } } private void populateClassNameFromClassURL() { populate(); if (className == null && classURL != null) { final String path = classURL.toString(); // .dll file? int index = (path.toLowerCase().indexOf(".dll!/")); if (index > 0) { className = path.substring(index + 6); return; } // Class within .jar or regular file path? index = path.lastIndexOf('!'); if (index > 0) { // .jar file className = path.substring(index + 2); } else { // file path className = path.substring(root.getURL().toString().length()); } // Remove the file extension from the class name index = className.lastIndexOf('.'); if (index > 0) { className = className.substring(0, index); } // Replace all file separators with dots (from file path the package/namespace) className = className.replaceAll("[\\\\\\/]", "."); } } private void populateHtmlURL() { populate(); if (htmlURL == null && classURL != null) { try { htmlURL = new URL(classURL.toString().replaceFirst(CLASS_EXTENSION, HTML_EXTENSION)); } catch (MalformedURLException ignore) {} } if (htmlURL == null && propertiesURL != null) { try { htmlURL = new URL(propertiesURL.toString().replaceFirst(PROPERTIES_EXTENSION, HTML_EXTENSION)); } catch (MalformedURLException ignore) {} } if (isInvalidURL(htmlURL)) { htmlURL = null; } } private boolean isInvalidURL(URL url) { if (url != null) { InputStream is = null; try { URLConnection conn = URLJarCollector.openConnection(url); is = conn.getInputStream(); return false; } catch (IOException e) { return true; } finally { FileUtil.cleanupStream(is); } } return true; } // / ------------------------------------- // / public // / ------------------------------------- public boolean isTeam() { return false; } public URL getHtmlURL() { // Lazy if (htmlURL == null) { populateHtmlURL(); } return htmlURL; } public URL getPropertiesURL() { if (propertiesURL == null) { populatePropertiesURLFromClassURL(); } return propertiesURL; } public Set<String> getFriendlyURLs() { populate(); Set<String> urls = new HashSet<String>(); URL url = null; if (classURL != null) { url = classURL; } else if (propertiesURL != null) { url = propertiesURL; } if (url != null) { String sUrl = url.toString(); String urlNoType = sUrl.substring(0, sUrl.lastIndexOf('.')); String path = url.getPath(); String pathNoType = path.substring(0, path.lastIndexOf('.')); urls.add(urlNoType); urls.add(pathNoType); } if (getFullClassName() != null) { if (RobocodeProperties.isTestingOn()) { urls.add(getFullClassName()); } else { urls.add(getUniqueFullClassName()); } urls.add(getUniqueFullClassNameWithVersion()); } if (root.isJAR()) { urls.add(root.getURL().toString()); } if (root instanceof ClasspathRoot) { String friendly = ((ClasspathRoot) root).getFriendlyProjectURL(itemUrl); if (friendly != null) { urls.add(friendly); } } return urls; } /** * {@inheritDoc} */ public void update(long lastModified, boolean force) { if (lastModified > this.lastModified || force) { if (force) { isValid = true; } // trying to guess all correct file locations populate(); this.lastModified = lastModified; if (classURL == null) { isValid = false; } loadProperties(); if (root.isJAR() && !isPropertiesLoaded) { isValid = false; } if (isValid) { validateType(false); } if (isValid) { verifyName(); } } } protected void validateType(boolean resolve) { populate(); final IHostManager hostManager = Container.getComponent(IHostManager.class); robotType = hostManager.getRobotType(this, resolve, classURL != null); if (!robotType.isValid()) { isValid = false; } } // Stronger than update public boolean validate() { validateType(true); return isValid; } // Note that ROBOCODE_CLASSNAME can be invalid, an hence can't be trusted when loaded! // Hence, we read the fullClassName field instead and NOT the ROBOCODE_CLASSNAME property. private boolean loadProperties() { if (!isPropertiesLoaded && propertiesURL != null) { InputStream ios = null; try { URLConnection con = URLJarCollector.openConnection(propertiesURL); ios = con.getInputStream(); properties.load(ios); isPropertiesLoaded = true; return true; } catch (IOException e) { return false; } finally { FileUtil.cleanupStream(ios); } } return false; } private boolean verifyName() { String robotName = getFullClassName(); String shortClassName = getShortClassName(); final boolean valid = verifyRobotName(robotName, shortClassName, false); if (!valid) { isValid = false; } return valid; } public static boolean verifyRobotName(String fullClassName, String shortClassName, boolean silent) { if (fullClassName == null || fullClassName.length() == 0 || fullClassName.contains("$")) { return false; } int lIndex = fullClassName.indexOf("."); if (lIndex > 0) { String rootPackage = fullClassName.substring(0, lIndex); if (rootPackage.equalsIgnoreCase("robocode")) { if (!silent) { logError("Robot " + fullClassName + " ignored. You cannot use package " + rootPackage); } return false; } if (rootPackage.length() > MAX_FULL_PACKAGE_NAME_LENGTH) { if (!silent) { logError( "Robot " + fullClassName + " has package name too long. " + MAX_FULL_PACKAGE_NAME_LENGTH + " characters maximum please."); } return false; } } if (shortClassName != null && shortClassName.length() > MAX_SHORT_CLASS_NAME_LENGTH) { if (!silent) { logError( "Robot " + fullClassName + " has classname too long. " + MAX_SHORT_CLASS_NAME_LENGTH + " characters maximum please."); } return false; } return true; } public void storeProperties(OutputStream os, RobotProperties robotProps) throws IOException { if (className != null) { properties.setProperty(ROBOT_CLASSNAME, className); } if (robotProps.getVersion() != null) { properties.setProperty(ROBOT_VERSION, robotProps.getVersion()); } if (robotProps.getDescription() != null) { properties.setProperty(ROBOT_DESCRIPTION, robotProps.getDescription()); } if (robotProps.getAuthor() != null) { properties.setProperty(ROBOT_AUTHOR_NAME, robotProps.getAuthor()); } if (robotProps.getWebPage() != null) { properties.setProperty(ROBOT_WEBPAGE, robotProps.getWebPage().toExternalForm()); } if (robotProps.getCodeSize() != null) { properties.setProperty(ROBOT_CODESIZE, "" + robotProps.getCodeSize()); } properties.setProperty(ROBOT_INCLUDE_SOURCE, "" + robotProps.isIncludeSource()); String version = Container.getComponent(IVersionManager.class).getVersion(); properties.setProperty(ROBOCODE_VERSION, version); saveProperties(os); saveProperties(); } private void saveProperties(OutputStream os) throws IOException { properties.store(os, "Robocode Robot"); } private void saveProperties() { File file = new File(root.getPath(), className.replaceAll("\\.", "/") + PROPERTIES_EXTENSION); FileOutputStream fos = null; try { fos = new FileOutputStream(file); saveProperties(fos); } catch (IOException e) { e.printStackTrace(); } finally { FileUtil.cleanupStream(fos); } populatePropertiesURLFromClassURL(); } public boolean isJuniorRobot() { return robotType.isJuniorRobot(); } public boolean isStandardRobot() { return robotType.isStandardRobot(); } public boolean isAdvancedRobot() { return robotType.isAdvancedRobot(); } public boolean isTeamRobot() { return robotType.isTeamRobot(); } public boolean isDroid() { return robotType.isDroid(); } public boolean isSentryRobot() { return robotType.isSentryRobot(); } public boolean isInteractiveRobot() { return robotType.isInteractiveRobot(); } public boolean isPaintRobot() { return robotType.isPaintRobot(); } public URL getClassPathURL() { return classPathURL; } public URL[] getSourcePathURLs() { // If no source path URL exists, we must use the class path URL if (sourcePathURLs.size() == 0) { return new URL[] { classPathURL }; } return sourcePathURLs.toArray(new URL[] {}); } public String getFullClassName() { // Lazy if (className == null) { populateClassNameFromClassURL(); } return className; } public String getVersion() { return properties.getProperty(ROBOT_VERSION, null); } public String getDescription() { return properties.getProperty(ROBOT_DESCRIPTION, null); } public String getAuthorName() { return properties.getProperty(ROBOT_AUTHOR_NAME, null); } public String getPlatform() { return properties.getProperty(ROBOT_PLATFORM, "Java"); } public URL getWebpage() { try { String webPage = properties.getProperty(ROBOT_WEBPAGE, null); if (webPage == null || webPage.trim().isEmpty()) { return null; } return new URL(webPage); } catch (MalformedURLException e) { return null; } } public Integer getCodeSize() { String value = properties.getProperty(ROBOT_CODESIZE); if (value == null) { return null; } try { return Integer.parseInt(value); } catch (NumberFormatException e) { return null; } } public boolean getIncludeSource() { return properties.getProperty(ROBOT_INCLUDE_SOURCE, "true").equalsIgnoreCase("true"); } public boolean getIncludeData() { return properties.getProperty(ROBOT_INCLUDE_DATA, "true").equalsIgnoreCase("true"); } public boolean isSourceIncluded() { return sourcePathURLs.size() > 0; } public String getRobocodeVersion() { return properties.getProperty(ROBOCODE_VERSION, null); } public String getReadableDirectory() { String path; if (root.isJAR()) { String jarFile = getClassPathURL().getFile(); jarFile = jarFile.substring(jarFile.lastIndexOf('/') + 1, jarFile.length()); path = FileUtil.getRobotsDataDir().getPath(); if (jarFile.length() > 0) { path += File.separator + jarFile + '_'; } } else { path = getClassPathURL().getFile(); } if (getFullPackage() != null) { path += File.separator + getFullPackage().replace('.', File.separatorChar); } return path; } public String getWritableDirectory() { String path; if (root.isJAR()) { String jarFile = getClassPathURL().getFile(); jarFile = jarFile.substring(jarFile.lastIndexOf('/') + 1, jarFile.length()); path = FileUtil.getRobotsDataDir().getPath(); if (jarFile.length() > 0) { path += File.separator + jarFile + '_'; } } else { path = ALWAYS_USE_CACHE_FOR_DATA ? FileUtil.getRobotsDataDir().getPath() : getClassPathURL().getFile(); } if (getFullPackage() != null) { path += File.separator + getFullPackage().replace('.', File.separatorChar); } return path; } public RobotSpecification createRobotSpecification(RobotSpecification battleRobotSpec, String teamId) { RobotSpecification specification; if (battleRobotSpec != null) { specification = battleRobotSpec; } else { specification = createRobotSpecification(); } if (teamId != null) { HiddenAccess.setTeamId(specification, teamId); } return specification; } public String toString() { return itemUrl.toString(); } }