001/* 002 * Copyright 2007-2018 The jdeb developers. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.vafer.jdeb.debian; 018 019import java.io.BufferedReader; 020import java.io.ByteArrayInputStream; 021import java.io.IOException; 022import java.io.InputStream; 023import java.io.InputStreamReader; 024import java.text.ParseException; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.HashSet; 028import java.util.LinkedHashMap; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032 033/** 034 * A control file as specified by the <a href="http://www.debian.org/doc/debian-policy/ch-controlfields.html">Debian policy</a>. 035 */ 036public abstract class ControlFile { 037 038 protected final Map<String, String> values = new LinkedHashMap<String, String>(); 039 protected final Map<String, String> userDefinedFields = new LinkedHashMap<String, String>(); 040 protected final Set<ControlField> userDefinedFieldNames = new HashSet<ControlField>(); 041 042 public void parse(String input) throws IOException, ParseException { 043 parse(new ByteArrayInputStream(input.getBytes("UTF-8"))); 044 } 045 046 public void parse(InputStream input) throws IOException, ParseException { 047 BufferedReader reader = new BufferedReader(new InputStreamReader(input, "UTF-8")); 048 StringBuilder buffer = new StringBuilder(); 049 String field = null; 050 int linenr = 0; 051 while (true) { 052 final String line = reader.readLine(); 053 054 if (line == null) { 055 // flush value of the previous field 056 set(field, buffer.toString()); 057 break; 058 } 059 060 linenr++; 061 062 if (line.length() == 0) { 063 throw new ParseException("Empty line", linenr); 064 } 065 066 final char first = line.charAt(0); 067 if (first == '#') { 068 // ignore commented out lines 069 continue; 070 } 071 072 if (Character.isLetter(first)) { 073 074 // new field 075 076 // flush value of the previous field 077 set(field, buffer.toString()); 078 buffer = new StringBuilder(); 079 080 081 final int i = line.indexOf(':'); 082 083 if (i < 0) { 084 throw new ParseException("Line misses ':' delimiter", linenr); 085 } 086 087 field = line.substring(0, i); 088 buffer.append(line.substring(i + 1).trim()); 089 090 continue; 091 } 092 093 // continuing old value, lines with only a dot are ignored 094 buffer.append('\n'); 095 if (!".".equals(line.substring(1).trim())) { 096 buffer.append(line.substring(1)); 097 } 098 } 099 reader.close(); 100 101 } 102 103 public void set(String field, final String value) { 104 if (field != null && isUserDefinedField(field)) { 105 userDefinedFields.put(field, value); 106 String fieldName = getUserDefinedFieldName(field); 107 108 if (fieldName != null) { 109 userDefinedFieldNames.add(new ControlField(fieldName)); 110 } 111 112 field = fieldName; 113 } 114 115 if (field != null && !"".equals(field)) { 116 values.put(field, value); 117 } 118 } 119 120 public String get(String field) { 121 return values.get(field); 122 } 123 124 protected abstract ControlField[] getFields(); 125 126 protected Map<String, String> getUserDefinedFields() { 127 return userDefinedFields; 128 } 129 130 protected Set<ControlField> getUserDefinedFieldNames() { 131 return userDefinedFieldNames; 132 } 133 134 public List<String> getMandatoryFields() { 135 List<String> fields = new ArrayList<String>(); 136 137 for (ControlField field : getFields()) { 138 if (field.isMandatory()) { 139 fields.add(field.getName()); 140 } 141 } 142 143 return fields; 144 } 145 146 public boolean isValid() { 147 return invalidFields().size() == 0; 148 } 149 150 public Set<String> invalidFields() { 151 Set<String> invalid = new HashSet<String>(); 152 153 for (ControlField field : getFields()) { 154 if (field.isMandatory() && get(field.getName()) == null) { 155 invalid.add(field.getName()); 156 } 157 } 158 159 return invalid; 160 } 161 162 public String toString(ControlField... fields) { 163 StringBuilder s = new StringBuilder(); 164 for (ControlField field : fields) { 165 String value = values.get(field.getName()); 166 s.append(field.format(value)); 167 } 168 return s.toString(); 169 } 170 171 public String toString() { 172 List<ControlField> fields = new ArrayList<ControlField>(); 173 fields.addAll(Arrays.asList(getFields())); 174 fields.addAll(getUserDefinedFieldNames()); 175 return toString(fields.toArray(new ControlField[fields.size()])); 176 } 177 178 /** 179 * Returns the letter expected in the prefix of a user defined field 180 * in order to include the field in this control file. 181 * 182 * @return The letter returned is: 183 * <ul> 184 * <li>B: for a binary package</li> 185 * <li>S: for a source package</li> 186 * <li>C: for a changes file</li> 187 * </ul> 188 * 189 * @since 1.1 190 * @see <a href="http://www.debian.org/doc/debian-policy/ch-controlfields.html#s5.7">Debian Policy - User-defined fields</a> 191 */ 192 protected abstract char getUserDefinedFieldLetter(); 193 194 /** 195 * Tells if the specified field name is a user defined field. 196 * User-defined fields must begin with an 'X', followed by one or more 197 * letters that specify the output file and a hyphen. 198 * 199 * @param field the name of the field 200 * 201 * @since 1.1 202 * @see <a href="http://www.debian.org/doc/debian-policy/ch-controlfields.html#s5.7">Debian Policy - User-defined fields</a> 203 */ 204 protected boolean isUserDefinedField(String field) { 205 return field.startsWith("X") && field.indexOf("-") > 0; 206 } 207 208 /** 209 * Returns the user defined field without its prefix. 210 * 211 * @param field the name of the user defined field 212 * @return the user defined field without the prefix, or null if the fields 213 * doesn't apply to this control file. 214 * @since 1.1 215 */ 216 protected String getUserDefinedFieldName(String field) { 217 int index = field.indexOf('-'); 218 char letter = getUserDefinedFieldLetter(); 219 220 for (int i = 0; i < index; ++i) { 221 if (field.charAt(i) == letter) { 222 return field.substring(index + 1); 223 } 224 } 225 226 return null; 227 } 228}