View Javadoc
1   /*
2    * Themis: Java Project Framework
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * 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, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.themis.solver.proj;
18  
19  import io.github.tonywasher.joceanus.metis.field.MetisFieldSet;
20  import io.github.tonywasher.joceanus.oceanus.format.OceanusDataFormatter;
21  import io.github.tonywasher.joceanus.themis.parser.base.ThemisChar;
22  import io.github.tonywasher.joceanus.themis.parser.base.ThemisDataResource;
23  import io.github.tonywasher.joceanus.themis.parser.project.ThemisFile;
24  import io.github.tonywasher.joceanus.themis.parser.project.ThemisPackage;
25  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverDef.ThemisSolverModuleDef;
26  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverDef.ThemisSolverPackageDef;
27  import io.github.tonywasher.joceanus.themis.solver.proj.ThemisSolverReference.ThemisRefType;
28  
29  import java.util.ArrayList;
30  import java.util.List;
31  
32  /**
33   * Solver Package.
34   */
35  public class ThemisSolverPackage
36          implements ThemisSolverPackageDef, Comparable<ThemisSolverPackage> {
37      /**
38       * Report fields.
39       */
40      private static final MetisFieldSet<ThemisSolverPackage> FIELD_DEFS = MetisFieldSet.newFieldSet(ThemisSolverPackage.class);
41  
42      /*
43       * Declare Fields.
44       */
45      static {
46          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_MODULE, ThemisSolverPackage::getOwningModule);
47          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_PARENT, ThemisSolverPackage::getParent);
48          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_CHILDREN, ThemisSolverPackage::getChildren);
49          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_FILES, ThemisSolverPackage::getFiles);
50          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_UNDERLYING, ThemisSolverPackage::getUnderlyingPackage);
51          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_REFERENCED, ThemisSolverPackage::getReferenceMap);
52          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_LOCALREFS, ThemisSolverPackage::getLocalReferences);
53          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_IMPLIEDREFS, ThemisSolverPackage::getImpliedReferences);
54          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_STANDARD, ThemisSolverPackage::isStandard);
55          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_CIRCULAR, ThemisSolverPackage::isCircular);
56          FIELD_DEFS.declareLocalField(ThemisDataResource.DATA_INCESTUOUS, ThemisSolverPackage::isIncestuous);
57      }
58  
59      /**
60       * The owning module.
61       */
62      private final ThemisSolverModuleDef theModule;
63  
64      /**
65       * The underlying package.
66       */
67      private final ThemisPackage thePackage;
68  
69      /**
70       * The shortName.
71       */
72      private final String theShortName;
73  
74      /**
75       * The files.
76       */
77      private final List<ThemisSolverFile> theFiles;
78  
79      /**
80       * The list of child packages.
81       */
82      private final List<ThemisSolverPackage> theChildren;
83  
84      /**
85       * The referenceMap.
86       */
87      private final ThemisSolverReference theReferenceMap;
88  
89      /**
90       * The referenced sibling packages in local package.
91       */
92      private final List<ThemisSolverPackageDef> theLocalReferences;
93  
94      /**
95       * The implied referenced sibling packages in local package.
96       */
97      private final List<ThemisSolverPackageDef> theImpliedReferences;
98  
99      /**
100      * Is this a standard package?
101      */
102     private final boolean isStandard;
103 
104     /**
105      * The parent package.
106      */
107     private ThemisSolverPackage theParent;
108 
109     /**
110      * Is the reference list circular?
111      */
112     private boolean isCircular;
113 
114     /**
115      * Is the package incestuous viz. its parent?
116      */
117     private boolean isIncestuous;
118 
119     /**
120      * Constructor.
121      *
122      * @param pModule  the owning module
123      * @param pPackage the parsed package
124      */
125     ThemisSolverPackage(final ThemisSolverModuleDef pModule,
126                         final ThemisPackage pPackage) {
127         /* Store the package and register with parser */
128         theModule = pModule;
129         thePackage = pPackage;
130         isStandard = pPackage.isStandard();
131 
132         /* Determine the short name */
133         final String myName = getPackageName();
134         final int iIndex = myName.lastIndexOf(ThemisChar.PERIOD);
135         theShortName = iIndex == -1 ? myName : myName.substring(iIndex + 1);
136 
137         /* Create the referenceMap and lists */
138         theReferenceMap = new ThemisSolverReference();
139         theLocalReferences = new ArrayList<>();
140         theImpliedReferences = new ArrayList<>();
141 
142         /* Populate the fileList */
143         theFiles = new ArrayList<>();
144         theChildren = new ArrayList<>();
145         for (ThemisFile myFile : thePackage.getFiles()) {
146             final ThemisSolverFile mySolverFile = new ThemisSolverFile(this, myFile);
147             theFiles.add(mySolverFile);
148         }
149     }
150 
151     @Override
152     public MetisFieldSet<ThemisSolverPackage> getDataFieldSet() {
153         return FIELD_DEFS;
154     }
155 
156     @Override
157     public String formatObject(final OceanusDataFormatter pFormatter) {
158         return toString();
159     }
160 
161     @Override
162     public ThemisSolverModuleDef getOwningModule() {
163         return theModule;
164     }
165 
166     @Override
167     public ThemisPackage getUnderlyingPackage() {
168         return thePackage;
169     }
170 
171     @Override
172     public String getPackageName() {
173         return thePackage.getPackage();
174     }
175 
176     /**
177      * Obtain the short package name.
178      *
179      * @return the shirt package name
180      */
181     public String getShortName() {
182         return theShortName;
183     }
184 
185     /**
186      * Obtain the file list.
187      *
188      * @return the file list
189      */
190     public List<ThemisSolverFile> getFiles() {
191         return theFiles;
192     }
193 
194     /**
195      * Obtain the list of child packages.
196      *
197      * @return the list of child packages
198      */
199     public List<ThemisSolverPackage> getChildren() {
200         return theChildren;
201     }
202 
203     /**
204      * Obtain the referenceMap.
205      *
206      * @return the referenceMap
207      */
208     public ThemisSolverReference getReferenceMap() {
209         return theReferenceMap;
210     }
211 
212     @Override
213     public List<ThemisSolverPackageDef> getLocalReferences() {
214         return theLocalReferences;
215     }
216 
217     /**
218      * Obtain the implied references.
219      *
220      * @return the implied references
221      */
222     public List<ThemisSolverPackageDef> getImpliedReferences() {
223         return theImpliedReferences;
224     }
225 
226     /**
227      * Add the child package.
228      *
229      * @param pChild the child package
230      */
231     public void addChild(final ThemisSolverPackage pChild) {
232         theChildren.add(pChild);
233         pChild.setParent(this);
234     }
235 
236     /**
237      * Set parent package.
238      *
239      * @param pParent the parent package
240      */
241     private void setParent(final ThemisSolverPackage pParent) {
242         theParent = pParent;
243     }
244 
245     /**
246      * Obtain the parent package.
247      *
248      * @return the parent
249      */
250     public ThemisSolverPackage getParent() {
251         return theParent;
252     }
253 
254     @Override
255     public boolean isStandard() {
256         return isStandard;
257     }
258 
259     /**
260      * Is the reference list circular?
261      *
262      * @return true/false
263      */
264     public boolean isCircular() {
265         return isCircular;
266     }
267 
268     /**
269      * Mark the package as incestuous.
270      */
271     public void markIncestuous() {
272         isIncestuous = true;
273     }
274 
275     /**
276      * Is the reference list incestuous?
277      *
278      * @return true/false
279      */
280     public boolean isIncestuous() {
281         return isIncestuous;
282     }
283 
284     /**
285      * Is the package a placeHolder?
286      *
287      * @return true/false
288      */
289     public boolean isPlaceHolder() {
290         return thePackage.isPlaceHolder();
291     }
292 
293     /**
294      * Do we have circular references in our files?
295      *
296      * @return true/false
297      */
298     public boolean areFilesCircular() {
299         /* Loop through files */
300         for (ThemisSolverFile myFile : theFiles) {
301             /* Report circularity */
302             if (myFile.isCircular()) {
303                 return true;
304             }
305         }
306 
307         /* We are OK */
308         return false;
309     }
310 
311     /**
312      * Are any of our children in error?
313      *
314      * @return true/false
315      */
316     private boolean areChildrenInError() {
317         /* Loop through children */
318         for (ThemisSolverPackage myChild : theChildren) {
319             /* Report error */
320             if (myChild.isInError()) {
321                 return true;
322             }
323         }
324 
325         /* We are OK */
326         return false;
327     }
328 
329     /**
330      * Is this package in error?
331      *
332      * @return true/false
333      */
334     public boolean isInError() {
335         return isCircular() || isIncestuous()
336                 || areChildrenInError() || areFilesCircular();
337     }
338 
339     /**
340      * Is this local package in error?
341      *
342      * @return true/false
343      */
344     public boolean isLocalInError() {
345         return isIncestuous() || areFilesCircular();
346     }
347 
348     /**
349      * Does the package lack child references?
350      *
351      * @return true/false
352      */
353     public boolean lackingChildReferences() {
354         return theReferenceMap.lackingReferences(ThemisRefType.CHILD);
355     }
356 
357     /**
358      * Set the referenced classes.
359      *
360      * @param pReferenced the referenced classes
361      */
362     public void setReferenced(final List<ThemisSolverPackageDef> pReferenced) {
363         /* Add all references except for a self-reference */
364         theLocalReferences.addAll(pReferenced.stream().filter(s -> !s.equals(this)).toList());
365     }
366 
367     /**
368      * process local references.
369      */
370     public void processLocalReferences() {
371         /* Loop through the referenced local classes */
372         for (ThemisSolverPackageDef myPackage : theLocalReferences) {
373             /* Process the class */
374             processLocalReferences(myPackage);
375         }
376 
377         /* Determine whether we are circular */
378         isCircular = theImpliedReferences.contains(this);
379     }
380 
381     /**
382      * process local references.
383      *
384      * @param pPackage the class to process local references for
385      */
386     private void processLocalReferences(final ThemisSolverPackageDef pPackage) {
387         /* If this is not already in the local reference list */
388         if (!theImpliedReferences.contains(pPackage)) {
389             /* Add the class */
390             theImpliedReferences.add(pPackage);
391 
392             /* Only process further if we have not found circularity */
393             if (!pPackage.equals(this)) {
394                 /* Loop through the local references */
395                 for (ThemisSolverPackageDef myPackage : pPackage.getLocalReferences()) {
396                     /* Process the local references */
397                     processLocalReferences(myPackage);
398                 }
399             }
400         }
401     }
402 
403     @Override
404     public boolean equals(final Object pThat) {
405         /* Handle the trivial cases */
406         if (this == pThat) {
407             return true;
408         }
409         if (pThat == null) {
410             return false;
411         }
412 
413         /* Make sure that the object is a Package */
414         if (!(pThat instanceof ThemisSolverPackage myThat)) {
415             return false;
416         }
417 
418         /* Check name of package */
419         return getPackageName().equals(myThat.getPackageName());
420     }
421 
422     @Override
423     public int hashCode() {
424         return getPackageName().hashCode();
425     }
426 
427     @Override
428     public String toString() {
429         return thePackage.toString();
430     }
431 
432     @Override
433     public int compareTo(final ThemisSolverPackage pThat) {
434         /* Handle simple dependency */
435         if (theImpliedReferences.contains(pThat)
436                 && !pThat.getImpliedReferences().contains(this)) {
437             return -1;
438         }
439         if (pThat.getImpliedReferences().contains(this)
440                 && !theImpliedReferences.contains(pThat)) {
441             return 1;
442         }
443 
444         /* Sort on number of dependencies */
445         final int iDiff = pThat.theImpliedReferences.size()
446                 - theImpliedReferences.size();
447         if (iDiff != 0) {
448             return iDiff;
449         }
450 
451         /* If all else fails rely on alphabetical */
452         return getPackageName().compareTo(pThat.getPackageName());
453     }
454 }