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