1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 *
17 */
18 package org.apache.tools.ant.taskdefs;
19
20 import java.io.File;
21 import java.io.UnsupportedEncodingException;
22
23 import org.apache.tools.ant.Task;
24 import org.apache.tools.ant.types.Path;
25 import org.apache.tools.ant.BuildException;
26 import org.apache.tools.ant.launch.Locator;
27 import org.apache.tools.ant.util.FileUtils;
28
29 /**
30 * Converts a Path into a property suitable as a Manifest classpath.
31 *
32 * @since Ant 1.7
33 *
34 * @ant.task category="property"
35 */
36 public class ManifestClassPath extends Task {
37
38 /** The property name to hold the classpath value. */
39 private String name;
40
41 /** The directory the classpath will be relative from. */
42 private File dir;
43
44 /** The maximum parent directory level to traverse. */
45 private int maxParentLevels = 2;
46
47 /** The classpath to convert. */
48 private Path path;
49
50 /**
51 * Sets a property, which must not already exist, with a space
52 * separated list of files and directories relative to the jar
53 * file's parent directory.
54 */
55 public void execute() {
56 if (name == null) {
57 throw new BuildException("Missing 'property' attribute!");
58 }
59 if (dir == null) {
60 throw new BuildException("Missing 'jarfile' attribute!");
61 }
62 if (getProject().getProperty(name) != null) {
63 throw new BuildException("Property '" + name + "' already set!");
64 }
65 if (path == null) {
66 throw new BuildException("Missing nested <classpath>!");
67 }
68
69 // Normalize the reference directory (containing the jar)
70 final FileUtils fileUtils = FileUtils.getFileUtils();
71 dir = fileUtils.normalize(dir.getAbsolutePath());
72
73 // Create as many directory prefixes as parent levels to traverse,
74 // in addition to the reference directory itself
75 File currDir = dir;
76 String[] dirs = new String[maxParentLevels + 1];
77 for (int i = 0; i < maxParentLevels + 1; ++i) {
78 dirs[i] = currDir.getAbsolutePath() + File.separatorChar;
79 currDir = currDir.getParentFile();
80 if (currDir == null) {
81 maxParentLevels = i + 1;
82 break;
83 }
84 }
85
86 String[] elements = path.list();
87 StringBuffer buffer = new StringBuffer();
88 StringBuffer element = new StringBuffer();
89 for (int i = 0; i < elements.length; ++i) {
90 // Normalize the current file
91 File pathEntry = new File(elements[i]);
92 pathEntry = fileUtils.normalize(pathEntry.getAbsolutePath());
93 String fullPath = pathEntry.getAbsolutePath();
94
95 // Find the longest prefix shared by the current file
96 // and the reference directory.
97 String relPath = null;
98 for (int j = 0; j <= maxParentLevels; ++j) {
99 String dir = dirs[j];
100 if (!fullPath.startsWith(dir)) {
101 continue;
102 }
103
104 // We have a match! Add as many ../ as parent
105 // directory traversed to get the relative path
106 element.setLength(0);
107 for (int k = 0; k < j; ++k) {
108 element.append("..");
109 element.append(File.separatorChar);
110 }
111 element.append(fullPath.substring(dir.length()));
112 relPath = element.toString();
113 break;
114 }
115
116 // No match, so bail out!
117 if (relPath == null) {
118 throw new BuildException(
119 "No suitable relative path from "
120 + dir + " to " + fullPath);
121 }
122
123 // Manifest's ClassPath: attribute always uses forward
124 // slashes '/', and is space-separated. Ant will properly
125 // format it on 72 columns with proper line continuation
126 if (File.separatorChar != '/') {
127 relPath = relPath.replace(File.separatorChar, '/');
128 }
129 if (pathEntry.isDirectory()) {
130 relPath = relPath + '/';
131 }
132 try {
133 relPath = Locator.encodeURI(relPath);
134 } catch (UnsupportedEncodingException exc) {
135 throw new BuildException(exc);
136 }
137 buffer.append(relPath);
138 buffer.append(' ');
139 }
140
141 // Finally assign the property with the manifest classpath
142 getProject().setNewProperty(name, buffer.toString().trim());
143 }
144
145 /**
146 * Sets the property name to hold the classpath value.
147 *
148 * @param name the property name
149 */
150 public void setProperty(String name) {
151 this.name = name;
152 }
153
154 /**
155 * The JAR file to contain the classpath attribute in its manifest.
156 *
157 * @param jarfile the JAR file. Need not exist yet, but its parent
158 * directory must exist on the other hand.
159 */
160 public void setJarFile(File jarfile) {
161 File parent = jarfile.getParentFile();
162 if (!parent.isDirectory()) {
163 throw new BuildException("Jar's directory not found: " + parent);
164 }
165 this.dir = parent;
166 }
167
168 /**
169 * Sets the maximum parent directory levels allowed when computing
170 * a relative path.
171 *
172 * @param levels the max level. Defaults to 2.
173 */
174 public void setMaxParentLevels(int levels) {
175 this.maxParentLevels = levels;
176 }
177
178 /**
179 * Adds the classpath to convert.
180 *
181 * @param path the classpath to convert.
182 */
183 public void addClassPath(Path path) {
184 this.path = path;
185 }
186
187 }