Parent: [r3] (diff)

Child: [r14] (diff)

Download this file

SPDXfile.java    561 lines (473 with data), 18.6 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
/*
* SPDXVersion: SPDX-1.1
* Person: Person: Nuno Brito (nuno.brito@triplecheck.de)
* Person: Organization: TripleCheck (contact@triplecheck.de)
* Date: 2013-08-29T00:00:00Z
* LicenseName: NOASSERTION
* FileName: SPDXfile.java
* FileType: SOURCE
* FileCopyrightText: <text> Copyright 2013 Nuno Brito, TripleCheck </text>
* FileComment: <text> Object that contains an SPDX report. This class
* contains the basic functions to read an SPDX file from disk, to perform
* the basic operations such as adding/changing/finding data and then
* saving this information back to a file on disk. </text>
*/
package spdxlib;
import definitions.Process;
import definitions.is;
import java.io.File;
import java.util.ArrayList;
import script.log;
import utils.files;
public final class SPDXfile {
// The variables listed here follow the same order as
// listed on the SPDX 1.1 specification
public SectionCreator
creatorSection = new SectionCreator();
public SectionPackage
packageSection = new SectionPackage();
ArrayList<TagLicense> // list of licenses applicable to this SPDX
licenses = new ArrayList();
public SectionFiles // where we report all found fileSection of the package
fileSection = new SectionFiles();
Person
reviewer;
TagValue
reviewDate,
reviewComment;
// basic objects to support the SPDX processing
public File
file; // direct file pointer to this file
public TagValueCollection
data = new TagValueCollection(); // where all data tags are placed
public String
rawText; // text for the SPDX file
String[]
lines; // where we keep all the lines that were separated
/**
* Constructor where we initialize this object by serving an SPDX text
* file as source of knowledge to fill up the contents
* @param file
*/
public SPDXfile(File file){
// assign the file pointer
this.file = file;
// do the normal reading
read(file);
}
// public SPDXfile(File file, String text) {
// // assign the file pointer
// this.file = file;
// rawText = text;
// // do the normal reading
// read(text);
// }
/**
* A reader for the version 1.1 of SPDX tag value format
* @param file The text file with the SPDX content
* @return SPDX object with the processed content from SPDX text file
*/
private void read(File file){
// preflight checks
// preflight checks
if(file == null){
return;
}
if(file.exists() == false){
return;
}
if(file.isDirectory()){
return;
}
// get the file pointer for future reference
data.tagFile = file;
// get the complete content of the file to a string file
rawText = utils.files.readAsString(file);
lines = rawText.split("\n");
// read all available data from the given file
data.read(lines);
// process all this information into meaningful data
processData();
}
/**
* Refreshes the data on this SPDX object with information directly
* provided from a text string
* @param text Content to be processed by the SPDX parser
*/
// public void read(String text){
// // read all available data from the given file
// data.read(text);
// // process all this information into meaningful data
// processData();
// }
/**
* Based on the information available on the "data" object, the SPDX
* document object will be filled with this information.
* @param file SPDX text file with the data to be interpreted
*/
private void processData(){
// reading is done in sequential mode, not very fast but it is robust
creatorSection.SPDXVersion = readGeneric(Keyword.SPDXVersion);
creatorSection.dataLicense = readGeneric(Keyword.DataLicense);
creatorSection.documentComment = readGeneric(Keyword.DocumentComment);
// read people information
parseSections();
creatorSection.created = readGeneric(Keyword.Created);
creatorSection.creatorComment = readGeneric(Keyword.CreatorComment);
}
/**
* Generic way of finding specific entries on the SPDX document, this
* only works for unique key titles.
*/
TagValue readGeneric(final Keyword keyword){
for(TagValue tag : data.tags){
if(tag.title.equalsIgnoreCase(keyword.toString())){
return tag;
}
}
return null;
}
/**
* Simply outputs to screen the basic data about this SPDX document
*/
void printHeader() {
String creatorsList = "\n";
for(Person person : creatorSection.people){
creatorsList = creatorsList.concat(person.toString()+"\n\n");
}
// System.out.println(
//// "SPDX version = " + SPDXVersion + "\n" +
//// "Data license = " + dataLicense + "\n" +
//// "Document Comment = " + documentComment + "\n" +
//// "Creators: " + creatorsList +
//// "Created = " + created + "\n" +
//// "Creator Comment = " + creatorComment + "\n" +
// "Reported files: " + fileSection.files.size()
// );
}
/**
* Attempts to discover which part of document is being processed.
* This helps to avoid a huge amount of IF statements for each tag processed
*/
int whichSectionShouldBeProcess(final TagValue tag, int currentPosition){
if(currentPosition < Process.header){
if(tag.title.equalsIgnoreCase(Keyword.SPDXVersion.toString())){
return Process.header;
}
}
// shall we start processing package data?
if( (currentPosition >= Process.header) &&
(currentPosition < Process.packageData)){
if(tag.title.equalsIgnoreCase
(Keyword.PackageName.toString())){
return Process.packageData;
}
}
// shall we start getting file data?
if( (currentPosition >= Process.packageData)&&
(currentPosition < Process.files)){
if(tag.title.equalsIgnoreCase
("FileName")){
return Process.files;
}
}
// if there is reviewer data, start processing
if( (currentPosition >= Process.files)&&
(currentPosition < Process.review)){
if(tag.title.substring(0, 6).equalsIgnoreCase("Review")){
return Process.review;
}
}
return currentPosition;
}
/**
* It has been noted in some cases that people are very flexible about the
* way how the people info is specified. So, it is necessary to go a bit
* beyond the specification and adapt to the odd cases in order to retrieve
* as much information as possible.
*/
void parseSections(){
/**
* There exist several tag/values across the document that can be
* errouneously mixed or misplaced. For example, the people info
* also appears on the package originator.
*
* Humans understand this information when inserted between two specific
* blocks of data. Our method needs to replicate this method.
*
* We will read sequentially all the found tag/value entries and signal
* markers whenever passing throught some specific block sections.
*/
// define our markers that will be used for positioning
int processing = Process.nothing;
// go through all tag/value entries
for(int i = 0; i < data.tags.size(); i++){
// get the current tag
TagValue tag = data.tags.get(i);
// do the markers check. No need to repeat them if already flagged
processing = whichSectionShouldBeProcess(tag, processing);
// TagValue abc = data.tags.get(i);
// if(i==xxx){
// System.out.println();
// }
///////////////////////////////////////////////////////////////////
// check for the specific blocks that we want to tap
///////////////////////////////////////////////////////////////////
switch(processing) {
case Process.header:
parseCreatorTags(tag);
break;
case Process.packageData:
packageSection.parseTag(tag);
parseOtherLicensingTags(tag);
break;
case Process.files:
fileSection.parseTag(tag);
break;
case Process.review:
parseReviewerTags(tag);
break;
}
}
// do the recovery attempts
if(fileSection.unknown.size() > 0){
for(TagValue unknown : fileSection.unknown){
tryRecovery(unknown);
}
}
}
/**
* Whenever a tag was found but was not allocated to a specific section
* then it might just be misplaced. We will try to see if it belongs
* to elsewhere in the document blocks
*/
void tryRecovery(TagValue tag){
packageSection.parseTag(tag);
}
/**
* Allows getting information from non-standard "Creator" tags onto a person
* object. If no valid information is valid, return person as null object
*/
void parseCreatorTags(TagValue tag){
// only process tags starting with "Creat"
if((tag.title.length() < 5)
|| (!tag.title.substring(0, 5).equalsIgnoreCase("Creat"))){
return;
}
//System.out.println(tag.title + " - " + tag);
// are we talking about custom people tags like WindRiver?
if(tag.title.equalsIgnoreCase("Creator")){
Person person = new Person();
creatorSection.people.add(person);
person.setTagPerson(tag);
}
// process strict Creator tag
if(tag.title.equalsIgnoreCase("Creator->Person")){
Person person = new Person();
creatorSection.people.add(person);
person.setTagPerson(tag);
}
// process organization details (we add them to last added person)
if(tag.title.equalsIgnoreCase("Creator->Organization")){
Person person = getLastCreator();
person.setTagOrganization(tag);
}
// process the tool details if available
if(tag.title.equalsIgnoreCase("Creator->Tool")){
Person person = getLastCreator();
person.setTagTool(tag);
}
}
/**
* Parse information regarding other licenses
* @param tag
*/
private void parseOtherLicensingTags(TagValue tag) {
if(tag.title.equalsIgnoreCase("LicenseID")){
TagLicense license = new TagLicense();
license.tagLicenseID = tag;
licenses.add(license);
return;
}
if(tag.title.equalsIgnoreCase("ExtractedText")){
TagLicense license = getLastAddedLicense();
license.tagExtractedText = tag;
return;
}
if(tag.title.equalsIgnoreCase("LicenseName")){
TagLicense license = getLastAddedLicense();
license.tagLicenseName = tag;
return;
}
if(tag.title.equalsIgnoreCase("LicenseCrossReference")){
TagLicense license = getLastAddedLicense();
license.tagLicenseCrossReference = tag;
return;
}
if(tag.title.equalsIgnoreCase("LicenseComment")){
TagLicense license = getLastAddedLicense();
license.tagLicenseComment = tag;
}
}
/**
* Goes the array of licenses and gets the most recent one.
* If there is none, it creates one.
*/
private TagLicense getLastAddedLicense(){
TagLicense result;
if(licenses.size()>0){
result = licenses.get(licenses.size()-1);
}else{
result = new TagLicense();
licenses.add(result);
}
return result;
}
/**
* Goes the array of people and gets the most recent one.
* If there is none, it creates one.
*/
private Person getLastCreator(){
Person result;
if(creatorSection.people.size()>0){
result = creatorSection.people.get(creatorSection.people.size()-1);
}else{
result = new Person();
creatorSection.people.add(result);
}
return result;
}
/**
* Do the reviewer tags now
*/
private void parseReviewerTags(TagValue tag) {
// only process tags starting with "Creat"
if((tag.title.length() < 6)
|| (!tag.title.substring(0, 5).equalsIgnoreCase("Review"))){
return;
}
// start processing these tags
if(tag.title.equalsIgnoreCase("Reviewer->Person")){
if(reviewer == null){
reviewer = new Person();
}
reviewer.setTagPerson(tag);
return;
}
if(tag.title.equalsIgnoreCase("Reviewer->Organizations")){
if(reviewer == null){
reviewer = new Person();
}
reviewer.setTagOrganization(tag);
return;
}
if(tag.title.equalsIgnoreCase("Reviewer->Tool")){
if(reviewer == null){
reviewer = new Person();
}
reviewer.setTagTool(tag);
return;
}
if(tag.title.equalsIgnoreCase("ReviewDate")){
reviewDate = tag;
return;
}
if(tag.title.equalsIgnoreCase("ReviewComment")){
reviewComment = tag;
}
}
public String getId(){
String title = packageSection.name.toString();
return title;
}
@Override
public String toString(){
String licenseOutput = "";
// if we have a license defined for this SPDX file, show it publicly
if(packageSection.licenseConcluded.toString().isEmpty() == false){
licenseOutput = " ("
+ packageSection.licenseConcluded.toString()+ ")";
}
return getId() + licenseOutput;
}
/**
* Adds another SPDX document as a dependency of this SPDX
* @param component
*/
public void addComponent(SPDXfile component){
// Add our component as a dependency
System.err.println("UY76 - Adding component "
+ component.file.getName());
/**
* Default format that we like:
*
* PackageDependency: busybox-1.20.2.tar.bz2.spdx
*
*/
// create the snippet of code that adds the dependency
String snippet = "\n" + "PackageDependency: "
+ component.file.getName();
// verify that this dependency was not added before
if(rawText.contains(snippet)){
// nothing necessary to be done
return;
}
// add this dependency to our text
rawText = rawText.concat(snippet);
// save the result to disk
utils.files.SaveStringToFile(file, rawText);
}
/**
* A critical feature is when we need to change a give tag inside the
* physical file on disk. This is not a straightforward action, might
* have different types of consequences that are not yet handled.
* @param tag The tag that we want to modify
* @param original The original text portion we want to change
* @param replacement The new string of text that will be written
* @return Returns the modified tag value or the same if nothing changed
*/
public TagValue changeTag(TagValue tag, String original, String replacement) {
// preflight checks
if(tag.isMultiLine){
log.write(is.ERROR, "SPDX change tag error, multi-line tags are "
+ "not yet supported: %1", tag.toString());
return tag;
}
if(tag.linePosition < 0){
log.write(is.ERROR, "SPDX change tag error, line position "
+ "is necessary but was not found: %1", "" + tag.linePosition);
return tag;
}
// do the change of text inside the tag
String newText = tag.raw.replace(original, replacement);
// save this info on the original tag object
tag.raw = newText;
// remove break lines, otherwise it adds a redundant line afterwards
newText = newText.replace("\n", "");
// replace the old line with the new contents
lines[tag.linePosition] = newText;
return tag;
}
/**
* After we are finished making changes, we can save the text file
*/
public void commitChanges(){
// save everything to disk
String modifiedText = "";
for(String line : lines){
modifiedText += line + "\n";
}
rawText = modifiedText;
// write file on disk
files.SaveStringToFile(file, rawText);
}
/**
* Whenever a given tag does not exist, there are cases when we need to add
* one. This method permits to add a tag right after one that already exists
* @param linePosition The tag used as anchor
* @param text the text to be included
*/
public void addTag(int linePosition, String text) {
lines[linePosition] += "\n" + text;
}
}