001: /*
002: * ====================================================================
003: * Copyright (c) 2004-2008 TMate Software Ltd. All rights reserved.
004: *
005: * This software is licensed as described in the file COPYING, which
006: * you should have received as part of this distribution. The terms
007: * are also available at http://svnkit.com/license.html
008: * If newer versions of this license are posted there, you may use a
009: * newer version instead, at your option.
010: * ====================================================================
011: */
012: package org.tmatesoft.svn.core.internal.wc;
013:
014: import java.io.File;
015: import java.util.Collection;
016: import java.util.HashMap;
017: import java.util.HashSet;
018: import java.util.Iterator;
019: import java.util.Map;
020: import java.util.StringTokenizer;
021:
022: import org.tmatesoft.svn.core.SVNErrorCode;
023: import org.tmatesoft.svn.core.SVNErrorMessage;
024: import org.tmatesoft.svn.core.SVNException;
025: import org.tmatesoft.svn.core.SVNNodeKind;
026: import org.tmatesoft.svn.core.SVNProperty;
027: import org.tmatesoft.svn.core.internal.wc.admin.SVNLog;
028: import org.tmatesoft.svn.core.internal.wc.admin.SVNAdminArea;
029: import org.tmatesoft.svn.core.internal.wc.admin.SVNEntry;
030: import org.tmatesoft.svn.core.internal.wc.admin.SVNTranslator;
031: import org.tmatesoft.svn.core.internal.wc.admin.SVNVersionedProperties;
032: import org.tmatesoft.svn.core.internal.wc.admin.SVNWCAccess;
033: import org.tmatesoft.svn.core.wc.ISVNOptions;
034: import org.tmatesoft.svn.core.wc.SVNStatusType;
035:
036: /**
037: * @version 1.1.1
038: * @author TMate Software Ltd.
039: */
040: public class SVNPropertiesManager {
041:
042: private static final Collection NOT_ALLOWED_FOR_FILE = new HashSet();
043: private static final Collection NOT_ALLOWED_FOR_DIR = new HashSet();
044:
045: static {
046: NOT_ALLOWED_FOR_FILE.add(SVNProperty.IGNORE);
047: NOT_ALLOWED_FOR_FILE.add(SVNProperty.EXTERNALS);
048:
049: NOT_ALLOWED_FOR_DIR.add(SVNProperty.EXECUTABLE);
050: NOT_ALLOWED_FOR_DIR.add(SVNProperty.KEYWORDS);
051: NOT_ALLOWED_FOR_DIR.add(SVNProperty.EOL_STYLE);
052: NOT_ALLOWED_FOR_DIR.add(SVNProperty.NEEDS_LOCK);
053: NOT_ALLOWED_FOR_DIR.add(SVNProperty.MIME_TYPE);
054: }
055:
056: public static void setWCProperty(SVNWCAccess access, File path,
057: String propName, String propValue, boolean write)
058: throws SVNException {
059: SVNEntry entry = access.getEntry(path, false);
060: if (entry == null) {
061: SVNErrorMessage err = SVNErrorMessage.create(
062: SVNErrorCode.UNVERSIONED_RESOURCE,
063: "''{0}'' is not under version control", path);
064: SVNErrorManager.error(err);
065: }
066: SVNAdminArea dir = entry.getKind() == SVNNodeKind.DIR ? access
067: .retrieve(path) : access.retrieve(path.getParentFile());
068: dir.getWCProperties(entry.getName()).setPropertyValue(propName,
069: propValue);
070: if (write) {
071: dir.saveWCProperties(false);
072: }
073: }
074:
075: public static String getWCProperty(SVNWCAccess access, File path,
076: String propName) throws SVNException {
077: SVNEntry entry = access.getEntry(path, false);
078: if (entry == null) {
079: return null;
080: }
081: SVNAdminArea dir = entry.getKind() == SVNNodeKind.DIR ? access
082: .retrieve(path) : access.retrieve(path.getParentFile());
083: return dir.getWCProperties(entry.getName()).getPropertyValue(
084: propName);
085: }
086:
087: public static void deleteWCProperties(SVNAdminArea dir,
088: String name, boolean recursive) throws SVNException {
089: if (name != null) {
090: SVNVersionedProperties props = dir.getWCProperties(name);
091: if (props != null) {
092: props.removeAll();
093: }
094: }
095: if (recursive || name == null) {
096: for (Iterator entries = dir.entries(false); entries
097: .hasNext();) {
098: SVNEntry entry = (SVNEntry) entries.next();
099: SVNVersionedProperties props = dir
100: .getWCProperties(entry.getName());
101: if (props != null) {
102: props.removeAll();
103: }
104: if (entry.isFile()
105: || dir.getThisDirName().equals(entry.getName())) {
106: continue;
107: }
108: if (recursive) {
109: SVNAdminArea childDir = dir.getWCAccess().retrieve(
110: dir.getFile(entry.getName()));
111: deleteWCProperties(childDir, null, true);
112: }
113: }
114: }
115: dir.saveWCProperties(false);
116: }
117:
118: public static String getProperty(SVNWCAccess access, File path,
119: String propName) throws SVNException {
120: SVNEntry entry = access.getEntry(path, false);
121: if (entry == null) {
122: return null;
123: }
124: String[] cachableProperties = entry.getCachableProperties();
125: if (cachableProperties != null
126: && contains(cachableProperties, propName)) {
127: String[] presentProperties = entry.getPresentProperties();
128: if (presentProperties == null
129: || !contains(presentProperties, propName)) {
130: return null;
131: }
132: if (SVNProperty.isBooleanProperty(propName)) {
133: return SVNProperty.getValueOfBooleanProperty(propName);
134: }
135: }
136: if (SVNProperty.isWorkingCopyProperty(propName)) {
137: return getWCProperty(access, path, propName);
138: } else if (SVNProperty.isEntryProperty(propName)) {
139: SVNErrorMessage err = SVNErrorMessage.create(
140: SVNErrorCode.BAD_PROP_KIND,
141: "Property ''{0}'' is an entry property", propName);
142: SVNErrorManager.error(err);
143: }
144: SVNAdminArea dir = entry.getKind() == SVNNodeKind.DIR ? access
145: .retrieve(path) : access.retrieve(path.getParentFile());
146: return dir.getProperties(entry.getName()).getPropertyValue(
147: propName);
148: }
149:
150: public static void setProperty(SVNWCAccess access, File path,
151: String propName, String propValue, boolean skipChecks)
152: throws SVNException {
153: if (SVNProperty.isWorkingCopyProperty(propName)) {
154: setWCProperty(access, path, propName, propValue, true);
155: return;
156: } else if (SVNProperty.isEntryProperty(propName)) {
157: SVNErrorMessage err = SVNErrorMessage.create(
158: SVNErrorCode.BAD_PROP_KIND,
159: "Property ''{0}'' is an entry property", propName);
160: SVNErrorManager.error(err);
161: }
162: SVNEntry entry = access.getEntry(path, false);
163: if (entry == null) {
164: SVNErrorMessage err = SVNErrorMessage.create(
165: SVNErrorCode.UNVERSIONED_RESOURCE,
166: "''{0}'' is not under version control", path);
167: SVNErrorManager.error(err);
168: }
169: SVNAdminArea dir = entry.getKind() == SVNNodeKind.DIR ? access
170: .retrieve(path) : access.retrieve(path.getParentFile());
171: boolean updateTimeStamp = SVNProperty.EOL_STYLE
172: .equals(propName);
173: if (propValue != null) {
174: validatePropertyName(path, propName, entry.getKind());
175: if (!skipChecks && SVNProperty.EOL_STYLE.equals(propName)) {
176: propValue = propValue.trim();
177: validateEOLProperty(path, access);
178: } else if (!skipChecks
179: && SVNProperty.MIME_TYPE.equals(propName)) {
180: propValue = propValue.trim();
181: validateMimeType(propValue);
182: } else if (SVNProperty.EXTERNALS.equals(propName)
183: || SVNProperty.IGNORE.equals(propName)) {
184: if (!propValue.endsWith("\n")) {
185: propValue += "\n";
186: }
187: if (SVNProperty.EXTERNALS.equals(propName)) {
188: // TODO validate
189: }
190: } else if (SVNProperty.KEYWORDS.equals(propName)) {
191: propValue = propValue.trim();
192: }
193: }
194: if (entry.getKind() == SVNNodeKind.FILE
195: && SVNProperty.EXECUTABLE.equals(propName)) {
196: if (propValue == null) {
197: SVNFileUtil.setExecutable(path, false);
198: } else {
199: propValue = SVNProperty
200: .getValueOfBooleanProperty(propName);
201: SVNFileUtil.setExecutable(path, true);
202: }
203: }
204: if (entry.getKind() == SVNNodeKind.FILE
205: && SVNProperty.NEEDS_LOCK.equals(propName)) {
206: if (propValue == null) {
207: SVNFileUtil.setReadonly(path, false);
208: } else {
209: propValue = SVNProperty
210: .getValueOfBooleanProperty(propName);
211: }
212: }
213: SVNVersionedProperties properties = dir.getProperties(entry
214: .getName());
215: if (!updateTimeStamp
216: && (entry.getKind() == SVNNodeKind.FILE && SVNProperty.KEYWORDS
217: .equals(propName))) {
218: String oldValue = properties
219: .getPropertyValue(SVNProperty.KEYWORDS);
220: Collection oldKeywords = getKeywords(oldValue);
221: Collection newKeywords = getKeywords(propValue);
222: updateTimeStamp = !oldKeywords.equals(newKeywords);
223: }
224: SVNLog log = dir.getLog();
225: if (updateTimeStamp) {
226: Map command = new HashMap();
227: command.put(SVNLog.NAME_ATTR, entry.getName());
228: command.put(SVNProperty
229: .shortPropertyName(SVNProperty.TEXT_TIME), null);
230: log.addCommand(SVNLog.MODIFY_ENTRY, command, false);
231: }
232: properties.setPropertyValue(propName, propValue);
233: dir.saveVersionedProperties(log, false);
234: log.save();
235: dir.runLogs();
236: }
237:
238: public static SVNStatusType mergeProperties(SVNWCAccess wcAccess,
239: File path, Map baseProperties, Map diff, boolean baseMerge,
240: boolean dryRun) throws SVNException {
241: SVNEntry entry = wcAccess.getEntry(path, false);
242: if (entry == null) {
243: SVNErrorMessage err = SVNErrorMessage.create(
244: SVNErrorCode.UNVERSIONED_RESOURCE,
245: "''{0}'' is not under version control", path);
246: SVNErrorManager.error(err);
247: }
248: File parent = null;
249: String name = null;
250: if (entry.isDirectory()) {
251: parent = path;
252: name = "";
253: } else if (entry.isFile()) {
254: parent = path.getParentFile();
255: name = entry.getName();
256: }
257:
258: SVNLog log = null;
259: SVNAdminArea dir = wcAccess.retrieve(parent);
260: if (!dryRun) {
261: log = dir.getLog();
262: }
263: SVNStatusType result = dir.mergeProperties(name,
264: baseProperties, diff, baseMerge, dryRun, log);
265: if (!dryRun) {
266: log.save();
267: dir.runLogs();
268: }
269: return result;
270: }
271:
272: public static Map computeAutoProperties(ISVNOptions options,
273: File file) {
274: Map properties = options.applyAutoProperties(file, null);
275: if (!properties.containsKey(SVNProperty.MIME_TYPE)) {
276: String mimeType = SVNFileUtil.detectMimeType(file);
277: if (mimeType != null) {
278: properties.put(SVNProperty.MIME_TYPE, mimeType);
279: }
280: }
281: if (SVNProperty.isBinaryMimeType((String) properties
282: .get(SVNProperty.MIME_TYPE))) {
283: properties.remove(SVNProperty.EOL_STYLE);
284: }
285: if (!properties.containsKey(SVNProperty.EXECUTABLE)) {
286: if (SVNFileUtil.isExecutable(file)) {
287: properties
288: .put(
289: SVNProperty.EXECUTABLE,
290: SVNProperty
291: .getValueOfBooleanProperty(SVNProperty.EXECUTABLE));
292: }
293: }
294: return properties;
295: }
296:
297: private static void validatePropertyName(File path, String name,
298: SVNNodeKind kind) throws SVNException {
299: SVNErrorMessage err = null;
300: if (kind == SVNNodeKind.DIR) {
301: if (NOT_ALLOWED_FOR_DIR.contains(name)) {
302: err = SVNErrorMessage.create(
303: SVNErrorCode.ILLEGAL_TARGET,
304: "Cannot set ''{0}'' on a directory (''{1}'')",
305: new Object[] { name, path });
306: }
307: } else if (kind == SVNNodeKind.FILE) {
308: if (NOT_ALLOWED_FOR_FILE.contains(name)) {
309: err = SVNErrorMessage.create(
310: SVNErrorCode.ILLEGAL_TARGET,
311: "Cannot set ''{0}'' on a file (''{1}'')",
312: new Object[] { name, path });
313: }
314: } else {
315: err = SVNErrorMessage.create(
316: SVNErrorCode.NODE_UNEXPECTED_KIND,
317: "''{0}'' is not a file or directory", path);
318: }
319: if (err != null) {
320: SVNErrorManager.error(err);
321: }
322: }
323:
324: private static void validateEOLProperty(File path,
325: SVNWCAccess access) throws SVNException {
326: String mimeType = getProperty(access, path,
327: SVNProperty.MIME_TYPE);
328: if (mimeType != null && SVNProperty.isBinaryMimeType(mimeType)) {
329: SVNErrorMessage err = SVNErrorMessage.create(
330: SVNErrorCode.ILLEGAL_TARGET,
331: "File ''{0}'' has binary mime type property", path);
332: SVNErrorManager.error(err);
333: }
334: boolean consistent = SVNTranslator.checkNewLines(path);
335: if (!consistent) {
336: SVNErrorMessage err = SVNErrorMessage.create(
337: SVNErrorCode.ILLEGAL_TARGET,
338: "File ''{0}'' has inconsistent newlines", path);
339: SVNErrorManager.error(err);
340: }
341: }
342:
343: private static void validateMimeType(String value)
344: throws SVNException {
345: String type = value.indexOf(';') >= 0 ? value.substring(0,
346: value.indexOf(';')) : value;
347: SVNErrorMessage err = null;
348: if (type.length() == 0) {
349: err = SVNErrorMessage.create(SVNErrorCode.BAD_MIME_TYPE,
350: "MIME type ''{0}'' has empty media type", value);
351: } else if (type.indexOf('/') < 0) {
352: err = SVNErrorMessage.create(SVNErrorCode.BAD_MIME_TYPE,
353: "MIME type ''{0}'' does not contain ''/''", value);
354: } else if (!Character.isLetterOrDigit(type
355: .charAt(type.length() - 1))) {
356: err = SVNErrorMessage
357: .create(
358: SVNErrorCode.BAD_MIME_TYPE,
359: "MIME type ''{0}'' ends with non-alphanumeric character",
360: value);
361: }
362: if (err != null) {
363: SVNErrorManager.error(err);
364: }
365: }
366:
367: private static Collection getKeywords(String value) {
368: Collection keywords = new HashSet();
369: if (value == null || "".equals(value.trim())) {
370: return keywords;
371: }
372: for (StringTokenizer tokens = new StringTokenizer(value,
373: " \t\n\r"); tokens.hasMoreTokens();) {
374: keywords.add(tokens.nextToken().toLowerCase());
375: }
376: return keywords;
377: }
378:
379: private static boolean contains(String[] values, String value) {
380: for (int i = 0; value != null && i < values.length; i++) {
381: if (values[i].equals(value)) {
382: return true;
383: }
384: }
385: return false;
386: }
387:
388: }
|