1 /*******************************************************************************
2  * Copyright (c) 2017 Till Brychcy and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     Till Brychcy - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.jdt.internal.compiler.env;
15 
16 import java.io.File;
17 import java.io.IOException;
18 import java.util.jar.JarFile;
19 import java.util.jar.Manifest;
20 
21 public class AutomaticModuleNaming {
22 
23 	private static final String AUTOMATIC_MODULE_NAME = "Automatic-Module-Name"; //$NON-NLS-1$
24 
25 	/**
26 	 * Determine the automatic module name of a given jar as specified in {@link <a href=
27 	 * "http://download.java.net/java/jdk9/docs/api/java/lang/module/ModuleFinder.html#of-java.nio.file.Path...-">
28 	 * ModuleFinder.of</a>}
29 	 */
determineAutomaticModuleName(final String jarFileName)30 	public static char[] determineAutomaticModuleName(final String jarFileName) {
31 		// "If the JAR file has the attribute "Automatic-Module-Name" in its main manifest then its value is the
32 		// module name."
33 		try (JarFile jar = new JarFile(jarFileName)) {
34 			Manifest manifest = jar.getManifest();
35 			if (manifest != null) {
36 				String automaticModuleName = manifest.getMainAttributes().getValue(AUTOMATIC_MODULE_NAME);
37 				if (automaticModuleName != null) {
38 					return automaticModuleName.toCharArray();
39 				}
40 			}
41 		} catch (IOException e) {
42 			// ignore
43 		}
44 		// The module name is otherwise derived from the name of the JAR file.
45 		return determineAutomaticModuleNameFromFileName(jarFileName, true, true);
46 	}
47 
48 	/**
49 	 * Determine the automatic module name of a given jar or project as specified in {@link <a href=
50 	 * "http://download.java.net/java/jdk9/docs/api/java/lang/module/ModuleFinder.html#of-java.nio.file.Path...-">
51 	 * ModuleFinder.of</a>}
52 	 * @param fileName names either a jar file or a java project in the workspace
53 	 * @param isFile <code>true</code> indicates that fileName denotes a file, <code>false</code> must be used for projects
54 	 * @param manifest representation of the META-INF/MANIFEST.MF entry within the given source (jar or project), or <code>null</code>
55 	 * @return the derived module name or <code>null</code>
56 	 */
determineAutomaticModuleName(final String fileName, boolean isFile, Manifest manifest)57 	public static char[] determineAutomaticModuleName(final String fileName, boolean isFile, Manifest manifest) {
58 		if (manifest != null) {
59 			String automaticModuleName = manifest.getMainAttributes().getValue(AUTOMATIC_MODULE_NAME);
60 			if (automaticModuleName != null) {
61 				return automaticModuleName.toCharArray();
62 			}
63 		}
64 		return determineAutomaticModuleNameFromFileName(fileName, true, isFile);
65 	}
66 
67 	/**
68 	 * Determine the automatic module name of a given jar or project as defined by an Automatic-Module-Name
69 	 * header in its manifest.
70 	 * @param manifest representation of the META-INF/MANIFEST.MF entry within the given source (jar or project), or <code>null</code>
71 	 * @return the derived module name or <code>null</code>
72 	 */
determineAutomaticModuleNameFromManifest(Manifest manifest)73 	public static char[] determineAutomaticModuleNameFromManifest(Manifest manifest) {
74 		if (manifest != null) {
75 			String automaticModuleName = manifest.getMainAttributes().getValue(AUTOMATIC_MODULE_NAME);
76 			if (automaticModuleName != null) {
77 				return automaticModuleName.toCharArray();
78 			}
79 		}
80 		return null;
81 	}
82 
83 	/**
84 	 * Determine the automatic module name if no "Automatic-Module-Name" was found in the Manifest, as specified in
85 	 * {@link <a href=
86 	 * "http://download.java.net/java/jdk9/docs/api/java/lang/module/ModuleFinder.html#of-java.nio.file.Path...-">ModuleFinder.of</a>}
87 	 *
88 	 * @param name
89 	 *            the filename (or directory name)
90 	 * @param skipDirectory
91 	 *            if true, parent directory names are skipped
92 	 * @param removeExtension
93 	 *            if true, the ".jar" extension is removed.
94 	 */
determineAutomaticModuleNameFromFileName(String name, boolean skipDirectory, boolean removeExtension)95 	public static char[] determineAutomaticModuleNameFromFileName(String name, boolean skipDirectory,
96 			boolean removeExtension) {
97 		int index;
98 		int start = 0;
99 		int end = name.length();
100 		if (skipDirectory) {
101 			index = name.lastIndexOf(File.separatorChar);
102 			start = index + 1;
103 		}
104 
105 		// "The ".jar" suffix is removed"
106 		if (removeExtension) {
107 			if (name.endsWith(".jar") || name.endsWith(".JAR")) { //$NON-NLS-1$//$NON-NLS-2$
108 				end -= 4;
109 			}
110 		}
111 
112 		// "If the name matches the regular expression "-(\\d+(\\.|$))" then the module name will be derived from the
113 		// subsequence preceding the hyphen of the first occurrence. [...]"
114 		dashLoop: for (index = start; index < end - 1; index++) {
115 			if (name.charAt(index) == '-' && name.charAt(index + 1) >= '0' && name.charAt(index + 1) <= '9') {
116 				for (int index2 = index + 2; index2 < end; index2++) {
117 					final char c = name.charAt(index2);
118 					if (c == '.') {
119 						break;
120 					}
121 					if (c < '0' || c > '9') {
122 						continue dashLoop;
123 					}
124 				}
125 				end = index;
126 				break;
127 			}
128 		}
129 
130 		// "All non-alphanumeric characters ([^A-Za-z0-9]) in the module name are replaced with a dot ("."), all
131 		// repeating dots are replaced with one dot, and all leading and trailing dots are removed."
132 		StringBuilder sb = new StringBuilder(end - start);
133 		boolean needDot = false;
134 		for (int i = start; i < end; i++) {
135 			char c = name.charAt(i);
136 			if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
137 				if (needDot) {
138 					sb.append('.');
139 					needDot = false;
140 				}
141 				sb.append(c);
142 			} else {
143 				if (sb.length() > 0) {
144 					needDot = true;
145 				}
146 			}
147 		}
148 		return sb.toString().toCharArray();
149 	}
150 
151 }
152