5555import org .eclipse .core .runtime .jobs .MultiRule ;
5656import org .eclipse .core .runtime .preferences .IEclipsePreferences ;
5757import org .eclipse .core .runtime .preferences .IExportedPreferences ;
58+ import org .eclipse .core .runtime .preferences .InstanceScope ;
5859import org .eclipse .osgi .util .NLS ;
5960import org .osgi .service .prefs .BackingStoreException ;
6061import org .osgi .service .prefs .Preferences ;
@@ -74,6 +75,12 @@ public class ProjectPreferences extends EclipsePreferences {
7475 * Cache which nodes have been loaded from disk
7576 */
7677 private static final Set <String > loadedNodes = ConcurrentHashMap .newKeySet ();
78+
79+ /**
80+ * Cache for project nesting relationships
81+ */
82+ private static final ProjectNestingCache nestingCache = new ProjectNestingCache ();
83+
7784 private IFile file ;
7885 private volatile boolean initialized ;
7986 /**
@@ -145,6 +152,8 @@ static void deleted(IFolder folder) throws CoreException {
145152 boolean hasResourcesSettings = getFile (folder , PREFS_REGULAR_QUALIFIER ).exists () || getFile (folder , PREFS_DERIVED_QUALIFIER ).exists ();
146153 // remove the preferences
147154 removeNode (projectNode );
155+ // Clear nesting cache as project structure changed
156+ nestingCache .clearCache ();
148157 // notifies the CharsetManager
149158 if (hasResourcesSettings ) {
150159 preferencesChanged (folder .getProject ());
@@ -165,6 +174,8 @@ static void deleted(IProject project) throws CoreException {
165174 boolean hasResourcesSettings = getFile (project , PREFS_REGULAR_QUALIFIER ).exists () || getFile (project , PREFS_DERIVED_QUALIFIER ).exists ();
166175 // remove the preferences
167176 removeNode (projectNode );
177+ // Clear nesting cache as project was deleted
178+ nestingCache .clearCache ();
168179 // notifies the CharsetManager
169180 if (hasResourcesSettings ) {
170181 preferencesChanged (project );
@@ -613,11 +624,97 @@ protected void load() throws BackingStoreException {
613624 }
614625
615626 private void load (boolean reportProblems ) throws BackingStoreException {
627+ // Load hierarchical preferences if enabled
628+ if (isHierarchicalPreferencesEnabled ()) {
629+ loadHierarchical (reportProblems );
630+ } else {
631+ loadSingle (reportProblems );
632+ }
633+ }
634+
635+ /**
636+ * Checks if hierarchical project preferences are enabled.
637+ */
638+ private boolean isHierarchicalPreferencesEnabled () {
639+ if (project == null || qualifier == null ) {
640+ return false ;
641+ }
642+ Preferences node = Platform .getPreferencesService ().getRootNode ()
643+ .node (InstanceScope .SCOPE )
644+ .node (ResourcesPlugin .PI_RESOURCES );
645+ return node .getBoolean (ResourcesPlugin .PREF_ENABLE_HIERARCHICAL_PROJECT_PREFERENCES ,
646+ ResourcesPlugin .DEFAULT_PREF_ENABLE_HIERARCHICAL_PROJECT_PREFERENCES );
647+ }
648+
649+ /**
650+ * Loads preferences hierarchically from ancestor projects.
651+ */
652+ private void loadHierarchical (boolean reportProblems ) throws BackingStoreException {
653+ List <IProject > ancestors = nestingCache .getAncestorProjects (project , getWorkspace ());
654+
655+ // Load from ancestors first (root to immediate parent)
656+ for (int i = ancestors .size () - 1 ; i >= 0 ; i --) {
657+ IProject ancestor = ancestors .get (i );
658+ IFile ancestorFile = getFile (ancestor , qualifier );
659+ if (ancestorFile != null && ancestorFile .exists ()) {
660+ loadPropertiesFromFile (ancestorFile , reportProblems );
661+ }
662+ }
663+
664+ // Finally, load from the project itself (which will override ancestor values)
665+ loadSingle (reportProblems );
666+ // Mark as loaded even if the local file doesn't exist (we may have inherited values)
667+ loadedNodes .add (absolutePath ());
668+ }
669+
670+ /**
671+ * Loads properties from a single file into this node.
672+ */
673+ private void loadPropertiesFromFile (IFile file , boolean reportProblems ) throws BackingStoreException {
674+ if (file == null || !file .exists ()) {
675+ return ;
676+ }
677+ if (Policy .DEBUG_PREFERENCES ) {
678+ Policy .debug ("Loading hierarchical preferences from file: " + file .getFullPath ()); //$NON-NLS-1$
679+ }
680+ Properties fromDisk = new Properties ();
681+ try (InputStream input = file .getContents (true )) {
682+ fromDisk .load (input );
683+ convertFromProperties (this , fromDisk , true );
684+ } catch (CoreException e ) {
685+ if (e .getStatus ().getCode () == IResourceStatus .RESOURCE_NOT_FOUND ) {
686+ if (Policy .DEBUG_PREFERENCES ) {
687+ Policy .debug ("Hierarchical preference file does not exist: " + file .getFullPath ()); //$NON-NLS-1$
688+ }
689+ return ;
690+ }
691+ if (reportProblems ) {
692+ String message = NLS .bind (Messages .preferences_loadException , file .getFullPath ());
693+ log (new Status (IStatus .ERROR , ResourcesPlugin .PI_RESOURCES , IStatus .ERROR , message , e ));
694+ throw new BackingStoreException (message );
695+ }
696+ } catch (IOException e ) {
697+ if (reportProblems ) {
698+ String message = NLS .bind (Messages .preferences_loadException , file .getFullPath ());
699+ log (new Status (IStatus .ERROR , ResourcesPlugin .PI_RESOURCES , IStatus .ERROR , message , e ));
700+ throw new BackingStoreException (message );
701+ }
702+ }
703+ }
704+
705+ /**
706+ * Loads preferences from a single file (non-hierarchical).
707+ */
708+ private void loadSingle (boolean reportProblems ) throws BackingStoreException {
616709 IFile localFile = getFile ();
617710 if (localFile == null || !localFile .exists ()) {
618711 if (Policy .DEBUG_PREFERENCES ) {
619712 Policy .debug ("Unable to determine preference file or file does not exist for node: " + absolutePath ()); //$NON-NLS-1$
620713 }
714+ // Mark as loaded even if file doesn't exist when in non-hierarchical mode
715+ if (!isHierarchicalPreferencesEnabled ()) {
716+ loadedNodes .add (absolutePath ());
717+ }
621718 return ;
622719 }
623720 if (Policy .DEBUG_PREFERENCES ) {
@@ -627,7 +724,10 @@ private void load(boolean reportProblems) throws BackingStoreException {
627724 try (InputStream input = localFile .getContents (true )) {
628725 fromDisk .load (input );
629726 convertFromProperties (this , fromDisk , true );
630- loadedNodes .add (absolutePath ());
727+ // Mark as loaded only in non-hierarchical mode (hierarchical mode marks at the end)
728+ if (!isHierarchicalPreferencesEnabled ()) {
729+ loadedNodes .add (absolutePath ());
730+ }
631731 } catch (CoreException e ) {
632732 if (e .getStatus ().getCode () == IResourceStatus .RESOURCE_NOT_FOUND ) {
633733 if (Policy .DEBUG_PREFERENCES ) {
0 commit comments